mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02: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
32 changed files with 720 additions and 143 deletions
|
@ -16,8 +16,7 @@
|
||||||
package ghidra.app.plugin.core.symtable;
|
package ghidra.app.plugin.core.symtable;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.TableModelListener;
|
import javax.swing.event.TableModelListener;
|
||||||
|
@ -213,5 +212,41 @@ class SymbolPanel extends JPanel {
|
||||||
}
|
}
|
||||||
return list;
|
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;
|
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?
|
// column for selecting/editing?
|
||||||
|
@ -478,7 +515,7 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
|
||||||
|
|
||||||
private static final String NO_SOURCE_TEXT = "None";
|
private static final String NO_SOURCE_TEXT = "None";
|
||||||
|
|
||||||
private GColumnRenderer<String> sourceCellRenderer = new AbstractGColumnRenderer<String>() {
|
private GColumnRenderer<String> sourceCellRenderer = new AbstractGColumnRenderer<>() {
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
|
||||||
|
@ -543,30 +580,28 @@ public class VTMarkupItemsTableModel extends AddressBasedTableModel<VTMarkupItem
|
||||||
static class IsInDBTableColumn
|
static class IsInDBTableColumn
|
||||||
extends AbstractProgramBasedDynamicTableColumn<VTMarkupItem, Boolean> {
|
extends AbstractProgramBasedDynamicTableColumn<VTMarkupItem, Boolean> {
|
||||||
|
|
||||||
private GColumnRenderer<Boolean> isInDBCellRenderer =
|
private GColumnRenderer<Boolean> isInDBCellRenderer = new AbstractGColumnRenderer<>() {
|
||||||
new AbstractGColumnRenderer<Boolean>() {
|
@Override
|
||||||
@Override
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
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 =
|
GTableCellRenderingData renderData = data.copyWithNewValue(isInDB ? "yes" : null);
|
||||||
data.copyWithNewValue(isInDB ? "yes" : null);
|
|
||||||
|
|
||||||
JLabel renderer = (JLabel) super.getTableCellRendererComponent(renderData);
|
JLabel renderer = (JLabel) super.getTableCellRendererComponent(renderData);
|
||||||
renderer.setOpaque(true);
|
renderer.setOpaque(true);
|
||||||
|
|
||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFilterString(Boolean t, Settings settings) {
|
public String getFilterString(Boolean t, Settings settings) {
|
||||||
boolean isInDB = t.booleanValue();
|
boolean isInDB = t.booleanValue();
|
||||||
return isInDB ? "yes" : "";
|
return isInDB ? "yes" : "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
|
|
@ -203,6 +203,41 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||||
|
|
||||||
return true;
|
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> {
|
static class MarkupStatusColumnComparator implements Comparator<VTMatch> {
|
||||||
|
@ -397,7 +432,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||||
return 55;
|
return 55;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<VTScore>() {
|
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<>() {
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
|
||||||
|
@ -457,7 +492,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||||
return 55;
|
return 55;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<VTScore>() {
|
private GColumnRenderer<VTScore> renderer = new AbstractGColumnRenderer<>() {
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
|
||||||
|
@ -550,7 +585,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||||
}
|
}
|
||||||
|
|
||||||
private GColumnRenderer<DisplayableLabel> labelCellRenderer =
|
private GColumnRenderer<DisplayableLabel> labelCellRenderer =
|
||||||
new AbstractGColumnRenderer<DisplayableLabel>() {
|
new AbstractGColumnRenderer<>() {
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
|
||||||
|
@ -681,7 +716,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||||
}
|
}
|
||||||
|
|
||||||
private GColumnRenderer<DisplayableAddress> addressCellRenderer =
|
private GColumnRenderer<DisplayableAddress> addressCellRenderer =
|
||||||
new AbstractGColumnRenderer<DisplayableAddress>() {
|
new AbstractGColumnRenderer<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
@ -788,7 +823,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||||
}
|
}
|
||||||
|
|
||||||
private GColumnRenderer<DisplayableLabel> labelCellRenderer =
|
private GColumnRenderer<DisplayableLabel> labelCellRenderer =
|
||||||
new AbstractGColumnRenderer<DisplayableLabel>() {
|
new AbstractGColumnRenderer<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
@ -921,7 +956,7 @@ public abstract class AbstractVTMatchTableModel extends AddressBasedTableModel<V
|
||||||
}
|
}
|
||||||
|
|
||||||
private GColumnRenderer<DisplayableAddress> addressCellRenderer =
|
private GColumnRenderer<DisplayableAddress> addressCellRenderer =
|
||||||
new AbstractGColumnRenderer<DisplayableAddress>() {
|
new AbstractGColumnRenderer<>() {
|
||||||
@Override
|
@Override
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.filter;
|
package docking.widgets.filter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public abstract class AbstractPatternTextFilter implements TextFilter {
|
public abstract class AbstractPatternTextFilter implements TextFilter {
|
||||||
|
|
||||||
protected final String filterText;
|
protected final String filterText;
|
||||||
private Pattern filterPattern;
|
protected Pattern filterPattern;
|
||||||
|
|
||||||
protected AbstractPatternTextFilter(String filterText) {
|
protected AbstractPatternTextFilter(String filterText) {
|
||||||
this.filterText = filterText;
|
this.filterText = filterText;
|
||||||
|
@ -67,6 +68,45 @@ public abstract class AbstractPatternTextFilter implements TextFilter {
|
||||||
return filterPattern;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
|
|
|
@ -24,13 +24,8 @@ import ghidra.util.UserSearchUtils;
|
||||||
*/
|
*/
|
||||||
public class ContainsTextFilter extends MatchesPatternTextFilter {
|
public class ContainsTextFilter extends MatchesPatternTextFilter {
|
||||||
|
|
||||||
private boolean caseSensitive;
|
|
||||||
private boolean allowGlobbing;
|
|
||||||
|
|
||||||
public ContainsTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
|
public ContainsTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
|
||||||
super(filterText);
|
super(filterText, caseSensitive, allowGlobbing);
|
||||||
this.caseSensitive = caseSensitive;
|
|
||||||
this.allowGlobbing = allowGlobbing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.filter;
|
package docking.widgets.filter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class InvertedTextFilter implements TextFilter {
|
public class InvertedTextFilter implements TextFilter {
|
||||||
|
|
||||||
private final TextFilter filter;
|
private final TextFilter filter;
|
||||||
|
@ -39,4 +41,30 @@ public class InvertedTextFilter implements TextFilter {
|
||||||
return filter.getFilterText();
|
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 {
|
public class MatchesExactlyTextFilter extends MatchesPatternTextFilter {
|
||||||
|
|
||||||
private boolean caseSensitive;
|
|
||||||
private boolean allowGlobbing;
|
|
||||||
|
|
||||||
public MatchesExactlyTextFilter(String filterText, boolean caseSensitive,
|
public MatchesExactlyTextFilter(String filterText, boolean caseSensitive,
|
||||||
boolean allowGlobbing) {
|
boolean allowGlobbing) {
|
||||||
super(filterText);
|
super(filterText, caseSensitive, allowGlobbing);
|
||||||
this.caseSensitive = caseSensitive;
|
|
||||||
this.allowGlobbing = allowGlobbing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -22,12 +22,55 @@ import java.util.regex.Pattern;
|
||||||
*/
|
*/
|
||||||
public abstract class MatchesPatternTextFilter extends AbstractPatternTextFilter {
|
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);
|
super(filterText);
|
||||||
|
|
||||||
|
this.caseSensitive = caseSensitive;
|
||||||
|
this.allowGlobbing = allowGlobbing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(String text, Pattern pattern) {
|
public boolean matches(String text, Pattern pattern) {
|
||||||
return pattern.matcher(text).matches();
|
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 {
|
public class StartsWithTextFilter extends MatchesPatternTextFilter {
|
||||||
|
|
||||||
private boolean caseSensitive;
|
|
||||||
private boolean allowGlobbing;
|
|
||||||
|
|
||||||
public StartsWithTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
|
public StartsWithTextFilter(String filterText, boolean caseSensitive, boolean allowGlobbing) {
|
||||||
super(filterText);
|
super(filterText, caseSensitive, allowGlobbing);
|
||||||
this.caseSensitive = caseSensitive;
|
|
||||||
this.allowGlobbing = allowGlobbing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
* fact that the data searched is retrieved from {@link #getModelData()}, which may be
|
||||||
* filtered.
|
* 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.
|
* @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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.table;
|
package docking.widgets.table;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines multiple Table Filters into a single TableFilter that can be applied. All contained
|
* 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;
|
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;
|
package docking.widgets.table;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.table.TableColumnModel;
|
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> {
|
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 TableColumnModel columnModel;
|
||||||
private final RowObjectTableModel<ROW_OBJECT> model;
|
private final RowObjectTableModel<ROW_OBJECT> model;
|
||||||
|
|
||||||
|
@ -129,4 +128,41 @@ public class DefaultRowFilterTransformer<ROW_OBJECT> implements RowFilterTransfo
|
||||||
(GColumnRenderer<Object>) column.getColumnRenderer();
|
(GColumnRenderer<Object>) column.getColumnRenderer();
|
||||||
return columnRenderer;
|
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.*;
|
import docking.widgets.filter.*;
|
||||||
|
|
||||||
public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
public class DefaultTableTextFilterFactory<ROW_OBJECT>
|
||||||
TableTextFilterFactory<ROW_OBJECT> {
|
implements TableTextFilterFactory<ROW_OBJECT> {
|
||||||
|
|
||||||
private final TextFilterFactory textFilterFactory;
|
private final TextFilterFactory textFilterFactory;
|
||||||
private final boolean inverted;
|
private final boolean inverted;
|
||||||
|
@ -40,7 +40,7 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
||||||
TableFilter<ROW_OBJECT> tableFilter = getBaseFilter(text, transformer);
|
TableFilter<ROW_OBJECT> tableFilter = getBaseFilter(text, transformer);
|
||||||
|
|
||||||
if (inverted && tableFilter != null) {
|
if (inverted && tableFilter != null) {
|
||||||
tableFilter = new InvertedTableFilter<ROW_OBJECT>(tableFilter);
|
tableFilter = new InvertedTableFilter<>(tableFilter);
|
||||||
}
|
}
|
||||||
return tableFilter;
|
return tableFilter;
|
||||||
}
|
}
|
||||||
|
@ -55,14 +55,14 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
||||||
if (textFilter == null) {
|
if (textFilter == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new TableTextFilter<ROW_OBJECT>(textFilter, transformer);
|
return new TableTextFilter<>(textFilter, transformer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TableFilter<ROW_OBJECT> getMultiWordTableFilter(String text,
|
private TableFilter<ROW_OBJECT> getMultiWordTableFilter(String text,
|
||||||
RowFilterTransformer<ROW_OBJECT> transformer) {
|
RowFilterTransformer<ROW_OBJECT> transformer) {
|
||||||
|
|
||||||
List<TextFilter> filters = new ArrayList<TextFilter>();
|
List<TextFilter> filters = new ArrayList<>();
|
||||||
TermSplitter splitter = filterOptions.getTermSplitter();
|
TermSplitter splitter = filterOptions.getTermSplitter();
|
||||||
for (String term : splitter.split(text)) {
|
for (String term : splitter.split(text)) {
|
||||||
TextFilter textFilter = textFilterFactory.getTextFilter(term);
|
TextFilter textFilter = textFilterFactory.getTextFilter(term);
|
||||||
|
@ -70,7 +70,7 @@ public class DefaultTableTextFilterFactory<ROW_OBJECT> implements
|
||||||
filters.add(textFilter);
|
filters.add(textFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new MultiTextFilterTableFilter<ROW_OBJECT>(text, filters, transformer,
|
return new MultiTextFilterTableFilter<>(filters, transformer,
|
||||||
filterOptions.getMultitermEvaluationMode());
|
filterOptions.getMultitermEvaluationMode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.table;
|
package docking.widgets.table;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
||||||
|
|
||||||
private final TableFilter<ROW_OBJECT> filter;
|
private final TableFilter<ROW_OBJECT> filter;
|
||||||
|
@ -34,4 +36,31 @@ public class InvertedTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT>
|
||||||
return !filter.acceptsRow(rowObject);
|
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;
|
package docking.widgets.table;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import docking.widgets.filter.MultitermEvaluationMode;
|
import docking.widgets.filter.MultitermEvaluationMode;
|
||||||
import docking.widgets.filter.TextFilter;
|
import docking.widgets.filter.TextFilter;
|
||||||
|
@ -23,13 +24,11 @@ import docking.widgets.filter.TextFilter;
|
||||||
public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
||||||
|
|
||||||
private final List<TextFilter> filters;
|
private final List<TextFilter> filters;
|
||||||
private final String text;
|
|
||||||
private final RowFilterTransformer<ROW_OBJECT> transformer;
|
private final RowFilterTransformer<ROW_OBJECT> transformer;
|
||||||
private final MultitermEvaluationMode evalMode;
|
private final MultitermEvaluationMode evalMode;
|
||||||
|
|
||||||
public MultiTextFilterTableFilter(String text, List<TextFilter> filters,
|
public MultiTextFilterTableFilter(List<TextFilter> filters,
|
||||||
RowFilterTransformer<ROW_OBJECT> transformer, MultitermEvaluationMode evalMode) {
|
RowFilterTransformer<ROW_OBJECT> transformer, MultitermEvaluationMode evalMode) {
|
||||||
this.text = text;
|
|
||||||
this.filters = filters;
|
this.filters = filters;
|
||||||
this.transformer = transformer;
|
this.transformer = transformer;
|
||||||
this.evalMode = evalMode;
|
this.evalMode = evalMode;
|
||||||
|
@ -90,4 +89,41 @@ public class MultiTextFilterTableFilter<ROW_OBJECT> implements TableFilter<ROW_O
|
||||||
// @formatter:on
|
// @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
|
* <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.
|
* 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
|
* @param t the item
|
||||||
* @return the view index
|
* @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
|
* <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.
|
* 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
|
* @param t the item
|
||||||
* @return the model index
|
* @return the model index
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package docking.widgets.table;
|
package docking.widgets.table;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import docking.widgets.filter.TextFilter;
|
import docking.widgets.filter.TextFilter;
|
||||||
|
|
||||||
|
@ -56,6 +57,38 @@ public class TableTextFilter<ROW_OBJECT> implements TableFilter<ROW_OBJECT> {
|
||||||
return false;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getClass().getSimpleName() + " - filter='" + textFilter.getFilterText() + "'";
|
return getClass().getSimpleName() + " - filter='" + textFilter.getFilterText() + "'";
|
||||||
|
|
|
@ -318,10 +318,6 @@ public class ColumnBasedTableFilter<R> implements TableFilter<R> {
|
||||||
list.add(constraintSet);
|
list.add(constraintSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return list.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean acceptsRow(R rowObject) {
|
boolean acceptsRow(R rowObject) {
|
||||||
for (ColumnConstraintSet<R, ?> constraintSet : list) {
|
for (ColumnConstraintSet<R, ?> constraintSet : list) {
|
||||||
if (!constraintSet.accepts(rowObject, tableFilterContext)) {
|
if (!constraintSet.accepts(rowObject, tableFilterContext)) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,17 +15,30 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.table.threaded;
|
package docking.widgets.table.threaded;
|
||||||
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import docking.widgets.table.AddRemoveListItem;
|
import docking.widgets.table.AddRemoveListItem;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class AddRemoveJob<T> extends TableUpdateJob<T> {
|
public class AddRemoveJob<T> extends TableUpdateJob<T> {
|
||||||
AddRemoveJob(ThreadedTableModel<T, ?> model, List<AddRemoveListItem<T>> addRemoveList,
|
AddRemoveJob(ThreadedTableModel<T, ?> model, List<AddRemoveListItem<T>> addRemoveList,
|
||||||
TaskMonitor monitor) {
|
TaskMonitor monitor) {
|
||||||
super(model, monitor);
|
super(model, monitor);
|
||||||
|
setForceFilter(false); // the item will do its own sorting and filtering
|
||||||
this.addRemoveList = addRemoveList;
|
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.
|
// to filter, which doesn't make sense if this is meant to only be used by itself.
|
||||||
return false;
|
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
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.table.threaded;
|
package docking.widgets.table.threaded;
|
||||||
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
import docking.widgets.table.TableSortingContext;
|
import docking.widgets.table.TableSortingContext;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class SortJob<T> extends TableUpdateJob<T> {
|
public class SortJob<T> extends TableUpdateJob<T> {
|
||||||
|
|
||||||
|
@ -30,10 +29,15 @@ public class SortJob<T> extends TableUpdateJob<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized boolean filter() {
|
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
|
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() {
|
TableData<ROW_OBJECT> copy() {
|
||||||
|
return copy(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableData<ROW_OBJECT> copy(TableData<ROW_OBJECT> newSource) {
|
||||||
List<ROW_OBJECT> dataCopy = new ArrayList<>(data);
|
List<ROW_OBJECT> dataCopy = new ArrayList<>(data);
|
||||||
TableData<ROW_OBJECT> newData = new TableData<>(dataCopy, sortContext);
|
TableData<ROW_OBJECT> newData = new TableData<>(dataCopy, sortContext);
|
||||||
newData.source = source;
|
newData.source = newSource;
|
||||||
newData.tableFilter = tableFilter;
|
newData.tableFilter = tableFilter;
|
||||||
newData.ID = ID; // it is a copy, but represents the same data
|
newData.ID = ID; // it is a copy, but represents the same data
|
||||||
return newData;
|
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
|
* @param t the item
|
||||||
* @return the index
|
* @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.
|
* @return true if the source data nor the filter are different that what is used by this object.
|
||||||
*/
|
*/
|
||||||
boolean matchesFilter(TableFilter<ROW_OBJECT> filter) {
|
boolean matchesFilter(TableFilter<ROW_OBJECT> filter) {
|
||||||
|
|
||||||
// O.K., we are derived from the same source data, if the filter is the same, then there
|
// 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);
|
return SystemUtilities.isEqual(tableFilter, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +294,16 @@ public class TableData<ROW_OBJECT> implements Iterable<ROW_OBJECT> {
|
||||||
return source.isUnrelatedTo(other);
|
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.
|
* Returns the root dataset for this data and all its ancestors.
|
||||||
* @return 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
|
* @param item the add/remove item to add to the list of items to be processed in the add/remove
|
||||||
* phase of this job.
|
* 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) {
|
public synchronized void addRemove(AddRemoveListItem<T> item, int maxAddRemoveCount) {
|
||||||
if (currentState != NOT_RUNNING) {
|
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
|
* 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
|
* the currently running job as well as the pending job. If called on the running job, the
|
||||||
* depends on the running job's state:
|
* effect depends on the running job's state:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>If the filter state hasn't happened yet, then nothing needs to be done as this job
|
* <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
|
* <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
|
* attempts to stop the current process phase and cause the state machine to
|
||||||
* filter phase.
|
* return to the filter phase.
|
||||||
* <li>If the current job has already entered the DONE state, then the filter cannot take
|
* <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
|
* effect in this job and a false value is returned to indicate the filter was
|
||||||
* filter was not handled by this job.
|
* not handled by this job.
|
||||||
* </ul>
|
* </ul>
|
||||||
* @return true if the filter can be processed by this job, false if this job is essentially already
|
* @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.
|
* completed and therefor cannot perform the filter job.
|
||||||
|
@ -239,7 +240,7 @@ public class TableUpdateJob<T> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (hasFiltered()) {
|
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();
|
monitor.cancel();
|
||||||
pendingRequestedState = FILTERING;
|
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 */
|
/** True if the sort applied to the table is not the same as that in the source dataset */
|
||||||
private boolean tableSortDiffersFromSourceData() {
|
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());
|
return !SystemUtilities.isEqual(sourceData.getSortContext(), model.getSortingContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,23 +606,24 @@ public class TableUpdateJob<T> {
|
||||||
|
|
||||||
List<T> list = filterSourceData.getData();
|
List<T> list = filterSourceData.getData();
|
||||||
List<T> result = model.doFilter(list, lastSortContext, monitor);
|
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
|
// the derived data is sorted the same as the source data
|
||||||
TableSortingContext<T> sortContext = filterSourceData.getSortContext();
|
TableSortingContext<T> sortContext = filterSourceData.getSortContext();
|
||||||
updatedData = TableData.createSubDataset(filterSourceData, result, sortContext);
|
updatedData = TableData.createSubDataset(filterSourceData, result, sortContext);
|
||||||
updatedData.setTableFilter(model.getTableFilter());
|
updatedData.setTableFilter(model.getTableFilter());
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// no filtering took place
|
|
||||||
updatedData = filterSourceData;
|
|
||||||
}
|
|
||||||
monitor.setMessage(
|
monitor.setMessage(
|
||||||
"Done filtering " + model.getName() + " (" + updatedData.size() + " rows)");
|
"Done filtering " + model.getName() + " (" + updatedData.size() + " rows)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyCurrentFilterData() {
|
private void copyCurrentFilterData() {
|
||||||
TableData<T> currentFilteredData = getCurrentFilteredData();
|
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
|
// We are re-using the filtered data, so use too its sort
|
||||||
lastSortContext = updatedData.getSortContext();
|
lastSortContext = updatedData.getSortContext();
|
||||||
|
|
|
@ -486,7 +486,11 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||||
|
|
||||||
SystemUtilities.assertThisIsTheSwingThread("Must be called on the Swing thread");
|
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.allData = allData;
|
||||||
this.filteredData = filteredData;
|
this.filteredData = filteredData;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import static org.junit.Assert.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -32,12 +33,15 @@ import ghidra.docking.spy.SpyEventRecorder;
|
||||||
import ghidra.framework.plugintool.ServiceProvider;
|
import ghidra.framework.plugintool.ServiceProvider;
|
||||||
import ghidra.util.task.TaskMonitor;
|
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 {
|
public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||||
|
|
||||||
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName());
|
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName());
|
||||||
private SpyTaskMonitor monitor = new SpyTaskMonitor();
|
private SpyTaskMonitor monitor = new SpyTaskMonitor();
|
||||||
private SpyTextFilter<Long> spyFilter;
|
private SpyTextFilter<Long> spyFilter;
|
||||||
private ThreadedTableModelListener spyLoadListener = new SpyTableModelListener();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TestDataKeyModel createTestModel() {
|
protected TestDataKeyModel createTestModel() {
|
||||||
|
@ -82,9 +86,11 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||||
Boolean.FALSE.toString());
|
Boolean.FALSE.toString());
|
||||||
|
|
||||||
waitForTableModel(model);
|
waitForTableModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
// must run in Swing so that we do not mutate listeners while events are broadcasting
|
@Override
|
||||||
runSwing(() -> model.addThreadedTableModelListener(spyLoadListener));
|
protected TestThreadedTableModelListener createListener() {
|
||||||
|
return new TestThreadedTableModelListener(model, recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -370,6 +376,89 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||||
assertRowCount(4); // matching values (for both filters): two, ten, ten, ten
|
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
|
// Private Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -449,6 +538,27 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||||
waitForSwing();
|
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,
|
private void createCombinedStartsWithFilter(String filterValue,
|
||||||
TableFilter<Long> secondFilter) {
|
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());
|
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
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -570,28 +684,53 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SpyTableModelListener implements ThreadedTableModelListener {
|
private class EmptyTextFilter implements TextFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadPending() {
|
public boolean matches(String text) {
|
||||||
recorder.record("Swing - model load pending");
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadingStarted() {
|
public String getFilterText() {
|
||||||
recorder.record("Swing - model load started");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadingFinished(boolean wasCancelled) {
|
public boolean isSubFilterOf(TextFilter filter) {
|
||||||
if (wasCancelled) {
|
return true;
|
||||||
recorder.record("Swing - model load cancelled");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
recorder.record("Swing - model load finsished; size: " + model.getRowCount());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
protected TestThreadedTableModelListener createListener() {
|
protected TestThreadedTableModelListener createListener() {
|
||||||
return new TestIncrementalThreadedTableModelListener(spy);
|
return new TestIncrementalThreadedTableModelListener(model, spy);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
|
@ -296,8 +296,8 @@ public class ThreadedTableTest extends AbstractThreadedTableTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAddSendsEvent() {
|
public void testAddSendsEvent() {
|
||||||
waitForTableModel(model);
|
waitForTableModel(model);
|
||||||
final AtomicReference<TableModelEvent> ref = new AtomicReference<>();
|
AtomicReference<TableModelEvent> ref = new AtomicReference<>();
|
||||||
model.addTableModelListener(e -> ref.set(e));
|
runSwing(() -> model.addTableModelListener(e -> ref.set(e)));
|
||||||
|
|
||||||
int newValue = model.getRowCount() + 1;
|
int newValue = model.getRowCount() + 1;
|
||||||
addItemToModel(newValue);
|
addItemToModel(newValue);
|
||||||
|
|
|
@ -59,13 +59,12 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||||
|
|
||||||
buildFrame();
|
buildFrame();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract TestDataKeyModel createTestModel();
|
protected abstract TestDataKeyModel createTestModel();
|
||||||
|
|
||||||
protected TestThreadedTableModelListener createListener() {
|
protected TestThreadedTableModelListener createListener() {
|
||||||
return new TestThreadedTableModelListener();
|
return new TestThreadedTableModelListener(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -257,8 +256,8 @@ public abstract class AbstractThreadedTableTest extends AbstractDockingTest {
|
||||||
|
|
||||||
protected void assertRowCount(int expectedCount) {
|
protected void assertRowCount(int expectedCount) {
|
||||||
int rowCount = model.getRowCount();
|
int rowCount = model.getRowCount();
|
||||||
assertThat("Have different number of table rows than expected after filtering",
|
assertThat("Have different number of table rows than expected after filtering", rowCount,
|
||||||
expectedCount, is(rowCount));
|
is(expectedCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertNoRowsFilteredOut() {
|
protected void assertNoRowsFilteredOut() {
|
||||||
|
|
|
@ -19,8 +19,9 @@ import ghidra.docking.spy.SpyEventRecorder;
|
||||||
|
|
||||||
public class TestIncrementalThreadedTableModelListener extends TestThreadedTableModelListener {
|
public class TestIncrementalThreadedTableModelListener extends TestThreadedTableModelListener {
|
||||||
|
|
||||||
TestIncrementalThreadedTableModelListener(SpyEventRecorder spy) {
|
TestIncrementalThreadedTableModelListener(ThreadedTableModel<?, ?> model,
|
||||||
super(spy);
|
SpyEventRecorder spy) {
|
||||||
|
super(model, spy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,46 +25,49 @@ public class TestThreadedTableModelListener implements ThreadedTableModelListene
|
||||||
private volatile boolean cancelled;
|
private volatile boolean cancelled;
|
||||||
|
|
||||||
private SpyEventRecorder spy;
|
private SpyEventRecorder spy;
|
||||||
|
private ThreadedTableModel<?, ?> model;
|
||||||
|
|
||||||
public TestThreadedTableModelListener() {
|
public TestThreadedTableModelListener(ThreadedTableModel<?, ?> model) {
|
||||||
this(new SpyEventRecorder("Listener Spy"));
|
this(model, new SpyEventRecorder("Listener Spy"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestThreadedTableModelListener(SpyEventRecorder spy) {
|
public TestThreadedTableModelListener(ThreadedTableModel<?, ?> model, SpyEventRecorder spy) {
|
||||||
this.spy = spy;
|
this.spy = spy;
|
||||||
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset(ThreadedTableModel<?, ?> model) {
|
void reset(ThreadedTableModel<?, ?> newModel) {
|
||||||
spy.record("listener - reset()");
|
spy.record("Test - listener - reset()");
|
||||||
completed = cancelled = false;
|
completed = cancelled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean doneWork() {
|
boolean doneWork() {
|
||||||
spy.record("listener - doneWork()? " + (completed || cancelled) + " - complted? " +
|
spy.record("Test - listener - doneWork()? " + (completed || cancelled) + " - completed? " +
|
||||||
completed + "; cancelled? " + cancelled);
|
completed + "; cancelled? " + cancelled);
|
||||||
return completed || cancelled;
|
return completed || cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean startedWork() {
|
boolean startedWork() {
|
||||||
spy.record("listener - startedWork() - updating? " + updating);
|
spy.record("Test - listener - startedWork() - updating? " + updating);
|
||||||
return updating;
|
return updating;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadPending() {
|
public void loadPending() {
|
||||||
spy.record("listener - loadPending()");
|
spy.record("Swing - listener - loadPending()");
|
||||||
pending = true;
|
pending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadingStarted() {
|
public void loadingStarted() {
|
||||||
spy.record("listener - loadStarted()");
|
spy.record("Swing - listener - loadStarted()");
|
||||||
updating = true;
|
updating = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadingFinished(boolean wasCancelled) {
|
public void loadingFinished(boolean wasCancelled) {
|
||||||
spy.record("listener - loadingFinished() - cancelled? " + wasCancelled);
|
spy.record("Swing - listener - loadingFinished() - cancelled? " + wasCancelled +
|
||||||
|
"; size: " + model.getRowCount());
|
||||||
cancelled = wasCancelled;
|
cancelled = wasCancelled;
|
||||||
completed = !cancelled;
|
completed = !cancelled;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.docking.spy;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.commons.lang3.time.FastDateFormat;
|
import org.apache.commons.lang3.time.FastDateFormat;
|
||||||
|
|
||||||
|
@ -33,20 +34,37 @@ public class SpyEventRecorder {
|
||||||
private String recorderName;
|
private String recorderName;
|
||||||
private List<SpyEvent> events = new ArrayList<>();
|
private List<SpyEvent> events = new ArrayList<>();
|
||||||
|
|
||||||
|
private AtomicBoolean buffered = new AtomicBoolean(true);
|
||||||
|
|
||||||
public SpyEventRecorder(String recorderName) {
|
public SpyEventRecorder(String recorderName) {
|
||||||
this.recorderName = recorderName;
|
this.recorderName = recorderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBuffered(boolean buffered) {
|
||||||
|
this.buffered.set(buffered);
|
||||||
|
}
|
||||||
|
|
||||||
// synchronized because we spy on multiple threads (like Test and Swing)
|
// synchronized because we spy on multiple threads (like Test and Swing)
|
||||||
public synchronized void record(String message) {
|
public synchronized void record(String message) {
|
||||||
SpyEvent event = new SpyEvent(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() {
|
private synchronized String eventsToString() {
|
||||||
|
|
||||||
|
int size = events.size();
|
||||||
|
int length = Integer.toString(size).length();
|
||||||
|
|
||||||
StringBuilder buffy = new StringBuilder("Recorded Events - " + recorderName + '\n');
|
StringBuilder buffy = new StringBuilder("Recorded Events - " + recorderName + '\n');
|
||||||
for (SpyEvent event : events) {
|
for (SpyEvent event : events) {
|
||||||
buffy.append(event.toString()).append('\n');
|
buffy.append(event.toString(length)).append('\n');
|
||||||
}
|
}
|
||||||
return buffy.toString();
|
return buffy.toString();
|
||||||
}
|
}
|
||||||
|
@ -63,6 +81,7 @@ public class SpyEventRecorder {
|
||||||
|
|
||||||
private class SpyEvent {
|
private class SpyEvent {
|
||||||
|
|
||||||
|
private static final String PADDING = " ";
|
||||||
private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ss:SSS");
|
private FastDateFormat dateFormat = FastDateFormat.getInstance("'T'HH:mm:ss:SSS");
|
||||||
|
|
||||||
private int id;
|
private int id;
|
||||||
|
@ -74,9 +93,13 @@ public class SpyEventRecorder {
|
||||||
this.id = ++globalId;
|
this.id = ++globalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
String toString(int idPad) {
|
||||||
public String toString() {
|
|
||||||
return "(" + id + ") " + dateFormat.format(time) + " " + message;
|
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;
|
return is;
|
||||||
}
|
}
|
||||||
|
|
||||||
URL url = getResource(testSearchPaths, filename);
|
URL url = getResource(getTestSearchPaths(), filename);
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue