mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
GT-2763 - Table Sorting - fixed bug that triggered filtering to take
place for individual add/remove operations; fixed bug that caused loss of added/removed items
This commit is contained in:
parent
da5f009c71
commit
445c7ca03e
@ -16,8 +16,7 @@
|
||||
package ghidra.app.plugin.core.symtable;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TableModelListener;
|
||||
@ -213,5 +212,41 @@ class SymbolPanel extends JPanel {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + getEnclosingInstance().hashCode();
|
||||
result = prime * result + ((list == null) ? 0 : list.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NameOnlyRowTransformer other = (NameOnlyRowTransformer) obj;
|
||||
if (!getEnclosingInstance().equals(other.getEnclosingInstance())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(list, other.list)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private SymbolPanel getEnclosingInstance() {
|
||||
return SymbolPanel.this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -243,6 +243,43 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + getEnclosingInstance().hashCode();
|
||||
result = prime * result + ((appliedFilters == null) ? 0 : appliedFilters.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MarkupTablePassthroughFilter other = (MarkupTablePassthroughFilter) obj;
|
||||
if (!getEnclosingInstance().equals(other.getEnclosingInstance())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(appliedFilters, other.appliedFilters)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private VTMarkupItemsTableModel getEnclosingInstance() {
|
||||
return VTMarkupItemsTableModel.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// column for selecting/editing?
|
||||
@ -478,7 +515,7 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
|
||||
|
||||
private static final String NO_SOURCE_TEXT = "None";
|
||||
|
||||
private GColumnRenderer<String> sourceCellRenderer = new AbstractGColumnRenderer<String>() {
|
||||
private GColumnRenderer<String> sourceCellRenderer = new AbstractGColumnRenderer<>() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
@ -543,30 +580,28 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
|
||||
static class IsInDBTableColumn
|
||||
extends AbstractProgramBasedDynamicTableColumn<VTMarkupItem, Boolean> {
|
||||
|
||||
private GColumnRenderer<Boolean> isInDBCellRenderer =
|
||||
new AbstractGColumnRenderer<Boolean>() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
private GColumnRenderer<Boolean> isInDBCellRenderer = new AbstractGColumnRenderer<>() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
Object value = data.getValue();
|
||||
Object value = data.getValue();
|
||||
|
||||
boolean isInDB = ((Boolean) value).booleanValue();
|
||||
boolean isInDB = ((Boolean) value).booleanValue();
|
||||
|
||||
GTableCellRenderingData renderData =
|
||||
data.copyWithNewValue(isInDB ? "yes" : null);
|
||||
GTableCellRenderingData renderData = data.copyWithNewValue(isInDB ? "yes" : null);
|
||||
|
||||
JLabel renderer = (JLabel) super.getTableCellRendererComponent(renderData);
|
||||
renderer.setOpaque(true);
|
||||
JLabel renderer = (JLabel) super.getTableCellRendererComponent(renderData);
|
||||
renderer.setOpaque(true);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(Boolean t, Settings settings) {
|
||||
boolean isInDB = t.booleanValue();
|
||||
return isInDB ? "yes" : "";
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public String getFilterString(Boolean t, Settings settings) {
|
||||
boolean isInDB = t.booleanValue();
|
||||
return isInDB ? "yes" : "";
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
|
@ -203,6 +203,41 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + getEnclosingInstance().hashCode();
|
||||
result = prime * result + ((appliedFilters == null) ? 0 : appliedFilters.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MatchTablePassthroughFilter other = (MatchTablePassthroughFilter) obj;
|
||||
if (!getEnclosingInstance().equals(other.getEnclosingInstance())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(appliedFilters, other.appliedFilters)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private AbstractVTMatchTableModel getEnclosingInstance() {
|
||||
return AbstractVTMatchTableModel.this;
|
||||
}
|
||||
}
|
||||
|
||||
static class MarkupStatusColumnComparator implements Comparator<VTMatch> {
|
||||
@ -397,7 +432,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||
return 55;
|
||||
}
|
||||
|
||||
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<VTScore>() {
|
||||
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<>() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
@ -457,7 +492,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||
return 55;
|
||||
}
|
||||
|
||||
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<VTScore>() {
|
||||
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<>() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
@ -550,7 +585,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||
}
|
||||
|
||||
private GColumnRenderer<DisplayableLabel> labelCellRenderer =
|
||||
new AbstractGColumnRenderer<DisplayableLabel>() {
|
||||
new AbstractGColumnRenderer<>() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
@ -681,7 +716,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||
}
|
||||
|
||||
private GColumnRenderer<DisplayableAddress> addressCellRenderer =
|
||||
new AbstractGColumnRenderer<DisplayableAddress>() {
|
||||
new AbstractGColumnRenderer<>() {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
@ -788,7 +823,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||
}
|
||||
|
||||
private GColumnRenderer<DisplayableLabel> labelCellRenderer =
|
||||
new AbstractGColumnRenderer<DisplayableLabel>() {
|
||||
new AbstractGColumnRenderer<>() {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
@ -921,7 +956,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||
}
|
||||
|
||||
private GColumnRenderer<DisplayableAddress> addressCellRenderer =
|
||||
new AbstractGColumnRenderer<DisplayableAddress>() {
|
||||
new AbstractGColumnRenderer<>() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
|
@ -15,12 +15,13 @@
|
||||
*/
|
||||
package docking.widgets.filter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class AbstractPatternTextFilter implements TextFilter {
|
||||
|
||||
protected final String filterText;
|
||||
private Pattern filterPattern;
|
||||
protected Pattern filterPattern;
|
||||
|
||||
protected AbstractPatternTextFilter(String filterText) {
|
||||
this.filterText = filterText;
|
||||
@ -67,6 +68,45 @@ public abstract class AbstractPatternTextFilter implements TextFilter {
|
||||
return filterPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((filterPattern == null) ? 0 : filterPattern.hashCode());
|
||||
result = prime * result + ((filterText == null) ? 0 : filterText.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AbstractPatternTextFilter other = (AbstractPatternTextFilter) obj;
|
||||
|
||||
String myPattern = getPatternString();
|
||||
String otherPattern = other.getPatternString();
|
||||
if (!myPattern.equals(otherPattern)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(filterText, other.filterText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getPatternString() {
|
||||
return filterPattern == null ? "" : filterPattern.pattern();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
//@formatter:off
|
||||
|
@ -24,13 +24,8 @@ import ghidra.util.UserSearchUtils;
|
||||
*/
|
||||
public class ContainsTextFilter extends MatchesPatternTextFilter {
|
||||
|
||||
private boolean caseSensitive;
|
||||
private boolean allowGlobbing;
|
||||
|
||||
public ContainsTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
|
||||
super(filterText);
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.allowGlobbing = allowGlobbing;
|
||||
super(filterText, caseSensitive, allowGlobbing);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package docking.widgets.filter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class InvertedTextFilter implements TextFilter {
|
||||
|
||||
private final TextFilter filter;
|
||||
@ -39,4 +41,30 @@ public class InvertedTextFilter implements TextFilter {
|
||||
return filter.getFilterText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((filter == null) ? 0 : filter.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InvertedTextFilter other = (InvertedTextFilter) obj;
|
||||
if (!Objects.equals(filter, other.filter)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -24,14 +24,9 @@ import ghidra.util.UserSearchUtils;
|
||||
*/
|
||||
public class MatchesExactlyTextFilter extends MatchesPatternTextFilter {
|
||||
|
||||
private boolean caseSensitive;
|
||||
private boolean allowGlobbing;
|
||||
|
||||
public MatchesExactlyTextFilter(String filterText, boolean caseSensitive,
|
||||
boolean allowGlobbing) {
|
||||
super(filterText);
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.allowGlobbing = allowGlobbing;
|
||||
super(filterText, caseSensitive, allowGlobbing);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,12 +22,55 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public abstract class MatchesPatternTextFilter extends AbstractPatternTextFilter {
|
||||
|
||||
public MatchesPatternTextFilter(String filterText) {
|
||||
protected boolean caseSensitive;
|
||||
protected boolean allowGlobbing;
|
||||
|
||||
public MatchesPatternTextFilter(String filterText, boolean caseSensitive,
|
||||
boolean allowGlobbing) {
|
||||
super(filterText);
|
||||
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.allowGlobbing = allowGlobbing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String text, Pattern pattern) {
|
||||
return pattern.matcher(text).matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + (allowGlobbing ? 1231 : 1237);
|
||||
result = prime * result + (caseSensitive ? 1231 : 1237);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MatchesPatternTextFilter other = (MatchesPatternTextFilter) obj;
|
||||
if (allowGlobbing != other.allowGlobbing) {
|
||||
return false;
|
||||
}
|
||||
if (caseSensitive != other.caseSensitive) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,13 +24,8 @@ import ghidra.util.UserSearchUtils;
|
||||
*/
|
||||
public class StartsWithTextFilter extends MatchesPatternTextFilter {
|
||||
|
||||
private boolean caseSensitive;
|
||||
private boolean allowGlobbing;
|
||||
|
||||
public StartsWithTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
|
||||
super(filterText);
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.allowGlobbing = allowGlobbing;
|
||||
super(filterText, caseSensitive, allowGlobbing);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -232,9 +232,6 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
|
||||
* fact that the data searched is retrieved from {@link #getModelData()}, which may be
|
||||
* filtered.
|
||||
*
|
||||
* <p>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
|
||||
*/
|
||||
|
@ -15,8 +15,7 @@
|
||||
*/
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Combines multiple Table Filters into a single TableFilter that can be applied. All contained
|
||||
@ -101,4 +100,32 @@ public class CombinedTableFilter<T> implements TableFilter<T> {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((filters == null) ? 0 : filters.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CombinedTableFilter<?> other = (CombinedTableFilter<?>) obj;
|
||||
if (!Objects.equals(filters, other.filters)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,8 +15,7 @@
|
||||
*/
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
@ -27,7 +26,7 @@ import ghidra.util.table.column.GColumnRenderer.ColumnConstraintFilterMode;
|
||||
|
||||
public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransformer<ROW_OBJECT> {
|
||||
|
||||
private List<String> columnData = new ArrayList<String>();
|
||||
private List<String> columnData = new ArrayList<>();
|
||||
private TableColumnModel columnModel;
|
||||
private final RowObjectTableModel<ROW_OBJECT> model;
|
||||
|
||||
@ -129,4 +128,41 @@ public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransfo
|
||||
(GColumnRenderer<Object>) column.getColumnRenderer();
|
||||
return columnRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((columnData == null) ? 0 : columnData.hashCode());
|
||||
result = prime * result + ((columnModel == null) ? 0 : columnModel.hashCode());
|
||||
result = prime * result + ((model == null) ? 0 : model.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DefaultRowFilterTransformer<?> other = (DefaultRowFilterTransformer<?>) obj;
|
||||
if (!Objects.equals(columnData, other.columnData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(columnModel, other.columnModel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(model, other.model)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ import java.util.List;
|
||||
|
||||
import docking.widgets.filter.*;
|
||||
|
||||
public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
||||
TableTextFilterFactory<ROW_OBJECT> {
|
||||
public class DefaultTableTextFilterFactory<ROW_OBJECT>
|
||||
implements TableTextFilterFactory<ROW_OBJECT> {
|
||||
|
||||
private final TextFilterFactory textFilterFactory;
|
||||
private final boolean inverted;
|
||||
@ -40,7 +40,7 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
||||
TableFilter<ROW_OBJECT> tableFilter = getBaseFilter(text, transformer);
|
||||
|
||||
if (inverted && tableFilter != null) {
|
||||
tableFilter = new InvertedTableFilter<ROW_OBJECT>(tableFilter);
|
||||
tableFilter = new InvertedTableFilter<>(tableFilter);
|
||||
}
|
||||
return tableFilter;
|
||||
}
|
||||
@ -55,14 +55,14 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
||||
if (textFilter == null) {
|
||||
return null;
|
||||
}
|
||||
return new TableTextFilter<ROW_OBJECT>(textFilter, transformer);
|
||||
return new TableTextFilter<>(textFilter, transformer);
|
||||
|
||||
}
|
||||
|
||||
private TableFilter<ROW_OBJECT> getMultiWordTableFilter(String text,
|
||||
RowFilterTransformer<ROW_OBJECT> transformer) {
|
||||
|
||||
List<TextFilter> filters = new ArrayList<TextFilter>();
|
||||
List<TextFilter> filters = new ArrayList<>();
|
||||
TermSplitter splitter = filterOptions.getTermSplitter();
|
||||
for (String term : splitter.split(text)) {
|
||||
TextFilter textFilter = textFilterFactory.getTextFilter(term);
|
||||
@ -70,7 +70,7 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
||||
filters.add(textFilter);
|
||||
}
|
||||
}
|
||||
return new MultiTextFilterTableFilter<ROW_OBJECT>(text, filters, transformer,
|
||||
return new MultiTextFilterTableFilter<>(filters, transformer,
|
||||
filterOptions.getMultitermEvaluationMode());
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
||||
|
||||
private final TableFilter<ROW_OBJECT> filter;
|
||||
@ -34,4 +36,31 @@ public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT>
|
||||
return !filter.acceptsRow(rowObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((filter == null) ? 0 : filter.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InvertedTableFilter<?> other = (InvertedTableFilter<?>) obj;
|
||||
if (!Objects.equals(filter, other.filter)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import docking.widgets.filter.MultitermEvaluationMode;
|
||||
import docking.widgets.filter.TextFilter;
|
||||
@ -23,13 +24,11 @@ import docking.widgets.filter.TextFilter;
|
||||
public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
||||
|
||||
private final List<TextFilter> filters;
|
||||
private final String text;
|
||||
private final RowFilterTransformer<ROW_OBJECT> transformer;
|
||||
private final MultitermEvaluationMode evalMode;
|
||||
|
||||
public MultiTextFilterTableFilter(String text, List<TextFilter> filters,
|
||||
public MultiTextFilterTableFilter(List<TextFilter> filters,
|
||||
RowFilterTransformer<ROW_OBJECT> transformer, MultitermEvaluationMode evalMode) {
|
||||
this.text = text;
|
||||
this.filters = filters;
|
||||
this.transformer = transformer;
|
||||
this.evalMode = evalMode;
|
||||
@ -90,4 +89,41 @@ public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_O
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((evalMode == null) ? 0 : evalMode.hashCode());
|
||||
result = prime * result + ((filters == null) ? 0 : filters.hashCode());
|
||||
result = prime * result + ((transformer == null) ? 0 : transformer.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MultiTextFilterTableFilter<?> other = (MultiTextFilterTableFilter<?>) obj;
|
||||
if (evalMode != other.evalMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(filters, other.filters)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(transformer, other.transformer)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,9 +64,6 @@ public interface RowObjectFilterModel<ROW_OBJECT> extends RowObjectTableModel<RO
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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
|
||||
*/
|
||||
@ -80,9 +77,6 @@ public interface RowObjectFilterModel<ROW_OBJECT> extends RowObjectTableModel<RO
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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
|
||||
*/
|
||||
|
@ -16,6 +16,7 @@
|
||||
package docking.widgets.table;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import docking.widgets.filter.TextFilter;
|
||||
|
||||
@ -56,6 +57,38 @@ public class TableTextFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((textFilter == null) ? 0 : textFilter.hashCode());
|
||||
result = prime * result + ((transformer == null) ? 0 : transformer.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TableTextFilter<?> other = (TableTextFilter<?>) obj;
|
||||
if (!Objects.equals(textFilter, other.textFilter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Objects.equals(transformer, other.transformer)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + " - filter='" + textFilter.getFilterText() + "'";
|
||||
|
@ -318,10 +318,6 @@ public class ColumnBasedTableFilter<R> implements TableFilter<R> {
|
||||
list.add(constraintSet);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
boolean acceptsRow(R rowObject) {
|
||||
for (ColumnConstraintSet<R, ?> constraintSet : list) {
|
||||
if (!constraintSet.accepts(rowObject, tableFilterContext)) {
|
||||
|
@ -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,17 +15,30 @@
|
||||
*/
|
||||
package docking.widgets.table.threaded;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.table.AddRemoveListItem;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class AddRemoveJob<T> extends TableUpdateJob<T> {
|
||||
AddRemoveJob(ThreadedTableModel<T, ?> model, List<AddRemoveListItem<T>> addRemoveList,
|
||||
TaskMonitor monitor) {
|
||||
super(model, monitor);
|
||||
setForceFilter(false); // the item will do its own sorting and filtering
|
||||
this.addRemoveList = addRemoveList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean filter() {
|
||||
//
|
||||
// This is a request to fully filter the table's data (like when the filter changes).
|
||||
// In this case, we had disabled 'force filter', as the sorting did not need it.
|
||||
// However, when the client asks to filter, make sure we filter.
|
||||
//
|
||||
boolean jobIsStillRunning = super.filter();
|
||||
if (jobIsStillRunning) {
|
||||
setForceFilter(true); // reset, since we had turned it off above; now we have to filter
|
||||
}
|
||||
return jobIsStillRunning;
|
||||
}
|
||||
}
|
||||
|
@ -38,4 +38,23 @@ public class NullTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
||||
// to filter, which doesn't make sense if this is meant to only be used by itself.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClass().hashCode();
|
||||
}
|
||||
}
|
||||
|
@ -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,8 @@
|
||||
*/
|
||||
package docking.widgets.table.threaded;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import docking.widgets.table.TableSortingContext;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class SortJob<T> extends TableUpdateJob<T> {
|
||||
|
||||
@ -30,10 +29,15 @@ public class SortJob<T> extends TableUpdateJob<T> {
|
||||
|
||||
@Override
|
||||
public synchronized boolean filter() {
|
||||
boolean canFilter = super.filter();
|
||||
if (canFilter) {
|
||||
//
|
||||
// This is a request to fully filter the table's data (like when the filter changes).
|
||||
// In this case, we had disabled 'force filter', as the sorting did not need it.
|
||||
// However, when the client asks to filter, make sure we filter.
|
||||
//
|
||||
boolean jobIsStillRunning = super.filter();
|
||||
if (jobIsStillRunning) {
|
||||
setForceFilter(true); // reset, since we had turned it off above; now we have to filter
|
||||
}
|
||||
return canFilter;
|
||||
return jobIsStillRunning;
|
||||
}
|
||||
}
|
||||
|
@ -83,9 +83,13 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
}
|
||||
|
||||
TableData<ROW_OBJECT> copy() {
|
||||
return copy(source);
|
||||
}
|
||||
|
||||
TableData<ROW_OBJECT> copy(TableData<ROW_OBJECT> newSource) {
|
||||
List<ROW_OBJECT> dataCopy = new ArrayList<>(data);
|
||||
TableData<ROW_OBJECT> newData = new TableData<>(dataCopy, sortContext);
|
||||
newData.source = source;
|
||||
newData.source = newSource;
|
||||
newData.tableFilter = tableFilter;
|
||||
newData.ID = ID; // it is a copy, but represents the same data
|
||||
return newData;
|
||||
@ -128,7 +132,8 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 when
|
||||
* sorted; a brute-force lookup when not sorted
|
||||
* @param t the item
|
||||
* @return the index
|
||||
*/
|
||||
@ -258,9 +263,11 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
* @return true if the source data nor the filter are different that what is used by this object.
|
||||
*/
|
||||
boolean matchesFilter(TableFilter<ROW_OBJECT> filter) {
|
||||
|
||||
// O.K., we are derived from the same source data, if the filter is the same, then there
|
||||
// is no need to refilter
|
||||
// is no need to refilter.
|
||||
//
|
||||
// Note: if a given filter does not override equals(), then this really means that they
|
||||
// must be the same filter for this method to return true
|
||||
return SystemUtilities.isEqual(tableFilter, filter);
|
||||
}
|
||||
|
||||
@ -287,6 +294,16 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||
return source.isUnrelatedTo(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of this table data. It is possible that two data instances of this class
|
||||
* that have the same ID are considered to be the same data.
|
||||
*
|
||||
* @return the ID
|
||||
*/
|
||||
int getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root dataset for this data and all its ancestors.
|
||||
* @return the root dataset for this data and all its ancestors.
|
||||
|
@ -166,7 +166,8 @@ public class TableUpdateJob<T> {
|
||||
*
|
||||
* @param item the add/remove item to add to the list of items to be processed in the add/remove
|
||||
* phase of this job.
|
||||
* @param the maximum number of add/remove jobs to queue before performing a full reload
|
||||
* @param maxAddRemoveCount the maximum number of add/remove jobs to queue before performing
|
||||
* a full reload
|
||||
*/
|
||||
public synchronized void addRemove(AddRemoveListItem<T> item, int maxAddRemoveCount) {
|
||||
if (currentState != NOT_RUNNING) {
|
||||
@ -219,17 +220,17 @@ public class TableUpdateJob<T> {
|
||||
|
||||
/**
|
||||
* Tells the job that the filter criteria has changed. This method can be called on
|
||||
* the currently running job as well as the pending job. If called on the running job, the effect
|
||||
* depends on the running job's state:
|
||||
* the currently running job as well as the pending job. If called on the running job, the
|
||||
* effect depends on the running job's state:
|
||||
* <ul>
|
||||
* <li>If the filter state hasn't happened yet, then nothing needs to be done as this job
|
||||
* will filter later anyway.
|
||||
* will filter later anyway.
|
||||
* <li>If the filter state has already been started or completed, then this method
|
||||
* attempts to stop the current process phase and cause the state machine to return to the
|
||||
* filter phase.
|
||||
* attempts to stop the current process phase and cause the state machine to
|
||||
* return to the filter phase.
|
||||
* <li>If the current job has already entered the DONE state, then the filter cannot take
|
||||
* effect in this job and a false value is returned to indicate the
|
||||
* filter was not handled by this job.
|
||||
* effect in this job and a false value is returned to indicate the filter was
|
||||
* not handled by this job.
|
||||
* </ul>
|
||||
* @return true if the filter can be processed by this job, false if this job is essentially already
|
||||
* completed and therefor cannot perform the filter job.
|
||||
@ -239,7 +240,7 @@ public class TableUpdateJob<T> {
|
||||
return false;
|
||||
}
|
||||
if (hasFiltered()) {
|
||||
// the user has requested a new filter, and we've already filtered, so we need to filter again
|
||||
// the user has requested a new filter; we've already filtered, so filter again
|
||||
monitor.cancel();
|
||||
pendingRequestedState = FILTERING;
|
||||
}
|
||||
@ -457,6 +458,11 @@ public class TableUpdateJob<T> {
|
||||
|
||||
/** True if the sort applied to the table is not the same as that in the source dataset */
|
||||
private boolean tableSortDiffersFromSourceData() {
|
||||
// Note: at this point in time we do not check to see if the table is user-unsorted. It
|
||||
// doesn't seem to hurt to leave the original source data sorted, even if the
|
||||
// current context is 'unsorted'. In that case, this method will return true,
|
||||
// that the sorts are different. But, later in this job, we check the new sort and
|
||||
// do not perform sorting when 'unsorted'
|
||||
return !SystemUtilities.isEqual(sourceData.getSortContext(), model.getSortingContext());
|
||||
}
|
||||
|
||||
@ -600,23 +606,24 @@ public class TableUpdateJob<T> {
|
||||
|
||||
List<T> list = filterSourceData.getData();
|
||||
List<T> result = model.doFilter(list, lastSortContext, monitor);
|
||||
if (result != list) { // yes, '=='
|
||||
if (result == list) { // yes, '=='
|
||||
// no filtering took place
|
||||
updatedData = filterSourceData;
|
||||
}
|
||||
else {
|
||||
// the derived data is sorted the same as the source data
|
||||
TableSortingContext<T> sortContext = filterSourceData.getSortContext();
|
||||
updatedData = TableData.createSubDataset(filterSourceData, result, sortContext);
|
||||
updatedData.setTableFilter(model.getTableFilter());
|
||||
}
|
||||
else {
|
||||
// no filtering took place
|
||||
updatedData = filterSourceData;
|
||||
}
|
||||
|
||||
monitor.setMessage(
|
||||
"Done filtering " + model.getName() + " (" + updatedData.size() + " rows)");
|
||||
}
|
||||
|
||||
private void copyCurrentFilterData() {
|
||||
TableData<T> currentFilteredData = getCurrentFilteredData();
|
||||
updatedData = currentFilteredData.copy(); // copy so we don't modify the UIs version
|
||||
updatedData = currentFilteredData.copy(sourceData); // copy; don't modify the UI's version
|
||||
|
||||
// We are re-using the filtered data, so use too its sort
|
||||
lastSortContext = updatedData.getSortContext();
|
||||
|
@ -486,7 +486,11 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||
|
||||
SystemUtilities.assertThisIsTheSwingThread("Must be called on the Swing thread");
|
||||
|
||||
boolean dataChanged = (this.filteredData.size() != filteredData.size());
|
||||
//@formatter:off
|
||||
// The data is changed when it is filtered OR when an item has been added or removed
|
||||
boolean dataChanged = this.filteredData.getId() != filteredData.getId() ||
|
||||
this.filteredData.size() != filteredData.size();
|
||||
//@formatter:on
|
||||
this.allData = allData;
|
||||
this.filteredData = filteredData;
|
||||
|
||||
|
@ -21,6 +21,7 @@ import static org.junit.Assert.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@ -32,12 +33,15 @@ import ghidra.docking.spy.SpyEventRecorder;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Specifically tests the sub-filtering behavior of the {@link ThreadedTableModel}, as well
|
||||
* as some other more complicated filtering combinations
|
||||
*/
|
||||
public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
|
||||
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName());
|
||||
private SpyTaskMonitor monitor = new SpyTaskMonitor();
|
||||
private SpyTextFilter<Long> spyFilter;
|
||||
private ThreadedTableModelListener spyLoadListener = new SpyTableModelListener();
|
||||
|
||||
@Override
|
||||
protected TestDataKeyModel createTestModel() {
|
||||
@ -82,9 +86,11 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
Boolean.FALSE.toString());
|
||||
|
||||
waitForTableModel(model);
|
||||
}
|
||||
|
||||
// must run in Swing so that we do not mutate listeners while events are broadcasting
|
||||
runSwing(() -> model.addThreadedTableModelListener(spyLoadListener));
|
||||
@Override
|
||||
protected TestThreadedTableModelListener createListener() {
|
||||
return new TestThreadedTableModelListener(model, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -370,6 +376,89 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
assertRowCount(4); // matching values (for both filters): two, ten, ten, ten
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCombinedFilter_AddRemove_ItemPassesFilter_FilterJobStateDoesNotRun() {
|
||||
|
||||
//
|
||||
// Tests that an item can be added/removed via addObject()/removeObject() *and* that,
|
||||
// with a *combined* filter installed, the *filter* phase of the TableLoadJob will *NOT*
|
||||
// get run. (The add/remove operation should perform filtering and sorting outside of
|
||||
// the normal TableLoadJob's state machine.)
|
||||
//
|
||||
|
||||
int fullCount = getRowCount();
|
||||
|
||||
createCombinedFilterWithEmptyTextFilter(new AllPassesTableFilter());
|
||||
assertFilteredEntireModel();
|
||||
assertRowCount(fullCount); // our filter passes everything
|
||||
|
||||
// call addObject()
|
||||
long newId = fullCount + 1;
|
||||
|
||||
spyFilter.reset();
|
||||
addItemToModel(newId);
|
||||
assertNumberOfItemsPassedThroughFilter(1); // **this is the important check**
|
||||
|
||||
assertRowCount(fullCount + 1); // our filter passes everything
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCombinedFilter_AddRemove_ItemFailsFilter_FilterJobStateDoesNotRun() {
|
||||
|
||||
//
|
||||
// Tests that an item can be added/removed via addObject()/removeObject() *and* that,
|
||||
// with a *combined* filter installed, the *filter* phase of the TableLoadJob will *NOT*
|
||||
// get run. (The add/remove operation should perform filtering and sorting outside of
|
||||
// the normal TableLoadJob's state machine.)
|
||||
//
|
||||
|
||||
int fullCount = getRowCount();
|
||||
|
||||
// use filter to limit any new items added from passing
|
||||
Predicate<Long> predicate = l -> l < fullCount;
|
||||
PredicateTableFilter noNewItemsPassFilter = new PredicateTableFilter(predicate);
|
||||
createCombinedFilterWithEmptyTextFilter(noNewItemsPassFilter);
|
||||
assertFilteredEntireModel();
|
||||
assertRowCount(fullCount); // our filter passes everything
|
||||
|
||||
// call addObject()
|
||||
long newId = fullCount + 1;
|
||||
spyFilter.reset();
|
||||
addItemToModel(newId);
|
||||
assertNumberOfItemsPassedThroughFilter(1); // **this is the important check**
|
||||
|
||||
assertRowCount(fullCount); // the new item should not be added
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCombinedFilter_AddRemove_ItemPassesFilter_RefilterThenUndo() throws Exception {
|
||||
|
||||
//
|
||||
// Bug Case: This was a case where a table (like the Symbol Table) that uses permanent
|
||||
// combined filters would lose items inserted via the addObject() call. The
|
||||
// issue is that the job was not properly updating the table's full source
|
||||
// data, only its filtered data. Thus, when a job triggered a reload from
|
||||
// the original source data, the value would be lost.
|
||||
//
|
||||
|
||||
int fullCount = getRowCount();
|
||||
|
||||
createCombinedFilterWithEmptyTextFilter(new AllPassesTableFilter());
|
||||
assertFilteredEntireModel();
|
||||
assertRowCount(fullCount); // our filter passes everything
|
||||
|
||||
// call addObject()
|
||||
long newId = fullCount + 1;
|
||||
addItemToModel(newId);
|
||||
assertRowCount(fullCount + 1); // our filter passes everything
|
||||
|
||||
filterOnRawColumnValue(newId);
|
||||
assertRowCount(1);
|
||||
|
||||
createCombinedFilterWithEmptyTextFilter(new AllPassesTableFilter());
|
||||
assertRowCount(fullCount + 1);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
@ -449,6 +538,27 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void createCombinedFilterWithEmptyTextFilter(TableFilter<Long> nonTextFilter) {
|
||||
|
||||
// the row objects are Long values that are 0-based one-up index values
|
||||
DefaultRowFilterTransformer<Long> transformer =
|
||||
new DefaultRowFilterTransformer<>(model, table.getColumnModel());
|
||||
|
||||
TextFilter allPassesFilter = new EmptyTextFilter();
|
||||
spyFilter = new SpyTextFilter<>(allPassesFilter, transformer, recorder);
|
||||
|
||||
CombinedTableFilter<Long> combinedFilter =
|
||||
new CombinedTableFilter<>(spyFilter, nonTextFilter, null);
|
||||
|
||||
recorder.record("Before setting the new filter");
|
||||
runSwing(() -> model.setTableFilter(combinedFilter));
|
||||
recorder.record("\tafter setting filter");
|
||||
|
||||
waitForNotBusy();
|
||||
waitForTableModel(model);
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void createCombinedStartsWithFilter(String filterValue,
|
||||
TableFilter<Long> secondFilter) {
|
||||
|
||||
@ -507,6 +617,10 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
assertTrue("The table did not filter data when it should have", spyFilter.hasFiltered());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void record(String message) {
|
||||
recorder.record("Test - " + message);
|
||||
}
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
@ -570,28 +684,53 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||
|
||||
}
|
||||
|
||||
private class SpyTableModelListener implements ThreadedTableModelListener {
|
||||
private class EmptyTextFilter implements TextFilter {
|
||||
|
||||
@Override
|
||||
public void loadPending() {
|
||||
recorder.record("Swing - model load pending");
|
||||
public boolean matches(String text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadingStarted() {
|
||||
recorder.record("Swing - model load started");
|
||||
public String getFilterText() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadingFinished(boolean wasCancelled) {
|
||||
if (wasCancelled) {
|
||||
recorder.record("Swing - model load cancelled");
|
||||
}
|
||||
else {
|
||||
recorder.record("Swing - model load finsished; size: " + model.getRowCount());
|
||||
}
|
||||
public boolean isSubFilterOf(TextFilter filter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class AllPassesTableFilter implements TableFilter<Long> {
|
||||
|
||||
@Override
|
||||
public boolean acceptsRow(Long rowObject) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class PredicateTableFilter implements TableFilter<Long> {
|
||||
|
||||
private Predicate<Long> predicate;
|
||||
|
||||
PredicateTableFilter(Predicate<Long> predicate) {
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsRow(Long rowObject) {
|
||||
return predicate.test(rowObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSubFilterOf(TableFilter<?> tableFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ public class IncrementalThreadedTableTest extends AbstractThreadedTableTest {
|
||||
|
||||
@Override
|
||||
protected TestThreadedTableModelListener createListener() {
|
||||
return new TestIncrementalThreadedTableModelListener(spy);
|
||||
return new TestIncrementalThreadedTableModelListener(model, spy);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
@ -296,8 +296,8 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||
@Test
|
||||
public void testAddSendsEvent() {
|
||||
waitForTableModel(model);
|
||||
final AtomicReference<TableModelEvent> ref = new AtomicReference<>();
|
||||
model.addTableModelListener(e -> ref.set(e));
|
||||
AtomicReference<TableModelEvent> ref = new AtomicReference<>();
|
||||
runSwing(() -> model.addTableModelListener(e -> ref.set(e)));
|
||||
|
||||
int newValue = model.getRowCount() + 1;
|
||||
addItemToModel(newValue);
|
||||
|
@ -59,13 +59,12 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||
|
||||
buildFrame();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected abstract TestDataKeyModel createTestModel();
|
||||
|
||||
protected TestThreadedTableModelListener createListener() {
|
||||
return new TestThreadedTableModelListener();
|
||||
return new TestThreadedTableModelListener(model);
|
||||
}
|
||||
|
||||
@After
|
||||
@ -257,8 +256,8 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||
|
||||
protected void assertRowCount(int expectedCount) {
|
||||
int rowCount = model.getRowCount();
|
||||
assertThat("Have different number of table rows than expected after filtering",
|
||||
expectedCount, is(rowCount));
|
||||
assertThat("Have different number of table rows than expected after filtering", rowCount,
|
||||
is(expectedCount));
|
||||
}
|
||||
|
||||
protected void assertNoRowsFilteredOut() {
|
||||
|
@ -19,8 +19,9 @@ import ghidra.docking.spy.SpyEventRecorder;
|
||||
|
||||
public class TestIncrementalThreadedTableModelListener extends TestThreadedTableModelListener {
|
||||
|
||||
TestIncrementalThreadedTableModelListener(SpyEventRecorder spy) {
|
||||
super(spy);
|
||||
TestIncrementalThreadedTableModelListener(ThreadedTableModel<?, ?> model,
|
||||
SpyEventRecorder spy) {
|
||||
super(model, spy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,46 +25,49 @@ public class TestThreadedTableModelListener implements ThreadedTableModelListene
|
||||
private volatile boolean cancelled;
|
||||
|
||||
private SpyEventRecorder spy;
|
||||
private ThreadedTableModel<?, ?> model;
|
||||
|
||||
public TestThreadedTableModelListener() {
|
||||
this(new SpyEventRecorder("Listener Spy"));
|
||||
public TestThreadedTableModelListener(ThreadedTableModel<?, ?> model) {
|
||||
this(model, new SpyEventRecorder("Listener Spy"));
|
||||
}
|
||||
|
||||
public TestThreadedTableModelListener(SpyEventRecorder spy) {
|
||||
public TestThreadedTableModelListener(ThreadedTableModel<?, ?> model, SpyEventRecorder spy) {
|
||||
this.spy = spy;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
void reset(ThreadedTableModel<?, ?> model) {
|
||||
spy.record("listener - reset()");
|
||||
void reset(ThreadedTableModel<?, ?> newModel) {
|
||||
spy.record("Test - listener - reset()");
|
||||
completed = cancelled = false;
|
||||
}
|
||||
|
||||
boolean doneWork() {
|
||||
spy.record("listener - doneWork()? " + (completed || cancelled) + " - complted? " +
|
||||
spy.record("Test - listener - doneWork()? " + (completed || cancelled) + " - completed? " +
|
||||
completed + "; cancelled? " + cancelled);
|
||||
return completed || cancelled;
|
||||
}
|
||||
|
||||
boolean startedWork() {
|
||||
spy.record("listener - startedWork() - updating? " + updating);
|
||||
spy.record("Test - listener - startedWork() - updating? " + updating);
|
||||
return updating;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPending() {
|
||||
spy.record("listener - loadPending()");
|
||||
spy.record("Swing - listener - loadPending()");
|
||||
pending = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadingStarted() {
|
||||
spy.record("listener - loadStarted()");
|
||||
spy.record("Swing - listener - loadStarted()");
|
||||
updating = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadingFinished(boolean wasCancelled) {
|
||||
spy.record("listener - loadingFinished() - cancelled? " + wasCancelled);
|
||||
spy.record("Swing - listener - loadingFinished() - cancelled? " + wasCancelled +
|
||||
"; size: " + model.getRowCount());
|
||||
cancelled = wasCancelled;
|
||||
completed = !cancelled;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package ghidra.docking.spy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.lang3.time.FastDateFormat;
|
||||
|
||||
@ -33,20 +34,37 @@ public class SpyEventRecorder {
|
||||
private String recorderName;
|
||||
private List<SpyEvent> events = new ArrayList<>();
|
||||
|
||||
private AtomicBoolean buffered = new AtomicBoolean(true);
|
||||
|
||||
public SpyEventRecorder(String recorderName) {
|
||||
this.recorderName = recorderName;
|
||||
}
|
||||
|
||||
public void setBuffered(boolean buffered) {
|
||||
this.buffered.set(buffered);
|
||||
}
|
||||
|
||||
// synchronized because we spy on multiple threads (like Test and Swing)
|
||||
public synchronized void record(String message) {
|
||||
SpyEvent event = new SpyEvent(message);
|
||||
events.add(event);
|
||||
|
||||
if (buffered.get()) {
|
||||
events.add(event);
|
||||
}
|
||||
else {
|
||||
// System.err intentional here for aesthetics
|
||||
System.err.println(event.toString(0));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized String eventsToString() {
|
||||
|
||||
int size = events.size();
|
||||
int length = Integer.toString(size).length();
|
||||
|
||||
StringBuilder buffy = new StringBuilder("Recorded Events - " + recorderName + '\n');
|
||||
for (SpyEvent event : events) {
|
||||
buffy.append(event.toString()).append('\n');
|
||||
buffy.append(event.toString(length)).append('\n');
|
||||
}
|
||||
return buffy.toString();
|
||||
}
|
||||
@ -63,6 +81,7 @@ public class SpyEventRecorder {
|
||||
|
||||
private class SpyEvent {
|
||||
|
||||
private static final String PADDING = " ";
|
||||
private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ss:SSS");
|
||||
|
||||
private int id;
|
||||
@ -74,9 +93,13 @@ public class SpyEventRecorder {
|
||||
this.id = ++globalId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + id + ") " + dateFormat.format(time) + " " + message;
|
||||
String toString(int idPad) {
|
||||
|
||||
int myLength = Integer.toString(id).length();
|
||||
int delta = Math.max(0, idPad - myLength);
|
||||
String pad = PADDING.substring(0, delta);
|
||||
|
||||
return "(" + id + ") " + pad + dateFormat.format(time) + " " + message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ public class ResourceManager {
|
||||
return is;
|
||||
}
|
||||
|
||||
URL url = getResource(testSearchPaths, filename);
|
||||
URL url = getResource(getTestSearchPaths(), filename);
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user