mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-5337 - Table Filters - fixed a bug that prevented a filter using an
escaped glob character from matching items
This commit is contained in:
parent
17419c57fe
commit
9be78bcd02
7 changed files with 422 additions and 103 deletions
|
@ -78,6 +78,7 @@ public class GhidraTableFilterTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
@Test
|
||||
public void testContains() {
|
||||
setFilterOptions(TextFilterStrategy.CONTAINS);
|
||||
|
||||
// no filter text - make sure all 5 nodes are there
|
||||
assertEquals(TOTAL_TABLE_ROWS, filteredModel.getRowCount());
|
||||
|
||||
|
|
|
@ -52,6 +52,10 @@ public class ContainsTextFilter extends MatchesPatternTextFilter {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (parentIsGlobEscape(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isSubFilter = filterText.contains(parent.filterText);
|
||||
return isSubFilter;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ package docking.widgets.filter;
|
|||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* A text filter that uses a pattern and performs a 'matches' using that pattern.
|
||||
*/
|
||||
|
@ -33,6 +35,26 @@ public abstract class MatchesPatternTextFilter extends AbstractPatternTextFilter
|
|||
this.allowGlobbing = allowGlobbing;
|
||||
}
|
||||
|
||||
protected boolean parentIsGlobEscape(MatchesPatternTextFilter parent) {
|
||||
|
||||
if (allowGlobbing) {
|
||||
// Handle escaped glob characters. A previous filter result that ends with an escape
|
||||
// character will not have correctly matched the items being filtered. Thus, we cannot
|
||||
// be a sub filter such a parent filter.
|
||||
boolean endsWithEscapedGlob = StringUtils.endsWithAny(filterText, "\\?", "\\*");
|
||||
if (endsWithEscapedGlob) {
|
||||
// If the user type slow enough (to let the SwingUpdateManager run) in the filter
|
||||
// field, then the parent filter will end with a backslash. If they user types fast
|
||||
// or pastes, then this will not be the case.
|
||||
if (parent.filterText.endsWith("\\")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String text, Pattern pattern) {
|
||||
return pattern.matcher(text).matches();
|
||||
|
|
|
@ -51,6 +51,10 @@ public class StartsWithTextFilter extends MatchesPatternTextFilter {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (parentIsGlobEscape(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isSubFilter = filterText.startsWith(parent.filterText);
|
||||
return isSubFilter;
|
||||
}
|
||||
|
|
|
@ -15,71 +15,76 @@
|
|||
*/
|
||||
package docking.widgets.table.threaded;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
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;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.table.JTableHeader;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.DockingFrame;
|
||||
import docking.test.AbstractDockingTest;
|
||||
import docking.widgets.filter.*;
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.docking.settings.Settings;
|
||||
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 {
|
||||
public class DefaultThreadedTableFilterTest extends AbstractDockingTest {
|
||||
|
||||
private SpyEventRecorder recorder = new SpyEventRecorder(getClass().getSimpleName());
|
||||
private SpyTaskMonitor monitor = new SpyTaskMonitor();
|
||||
private SpyTextFilter<Long> spyFilter;
|
||||
private SpyTextFilter<TestRowObject> spyFilter;
|
||||
|
||||
@Override
|
||||
protected TestDataKeyModel createTestModel() {
|
||||
AtomicReference<TestDataKeyModel> ref = new AtomicReference<>();
|
||||
protected TestThreadedTableModel model;
|
||||
protected GTable table;
|
||||
protected JTableHeader header;
|
||||
protected JFrame frame;
|
||||
protected TestThreadedTableModelListener testTableModelListener;
|
||||
protected GThreadedTablePanel<TestRowObject> threadedTablePanel;
|
||||
protected volatile boolean isDisposing = false;
|
||||
|
||||
private TestThreadedTableModel createTestModel() {
|
||||
|
||||
// Note: from the test model, the data looks like this:
|
||||
// "one", "two", "THREE", "Four", "FiVe", "sIx", "SeVEn", "EighT", "NINE",
|
||||
// "ten", "ten", "ten"
|
||||
runSwing(() -> ref.set(new TestDataKeyModel(monitor, false) {
|
||||
return runSwing(() -> new TestThreadedTableModel() {
|
||||
@Override
|
||||
void setDefaultTaskMonitor(TaskMonitor monitor) {
|
||||
// No! some of our tests use a spy monitor. If you ever find that you
|
||||
// need the standard monitors to get wired, then wrap the monitor being
|
||||
// passed-in here by the spy and let it delegate whilst recording messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncrementalTaskMonitor(TaskMonitor monitor) {
|
||||
// no! some of our tests use a spy monitor
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<Long> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<Long> descriptor = super.createTableColumnDescriptor();
|
||||
|
||||
// add our own custom column to test filtering
|
||||
descriptor.addVisibleColumn(new RawRowValueTableColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}));
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
model = createTestModel();
|
||||
testTableModelListener = createListener();
|
||||
model.addThreadedTableModelListener(testTableModelListener);
|
||||
|
||||
// do this in swing, as some of the table column setup can trigger concurrent modifications
|
||||
// due to the swing and the test working on the widgets at the same time
|
||||
runSwing(() -> {
|
||||
threadedTablePanel = new GThreadedTablePanel<>(model);
|
||||
table = threadedTablePanel.getTable();
|
||||
header = table.getTableHeader();
|
||||
|
||||
buildFrame(threadedTablePanel);
|
||||
});
|
||||
|
||||
// Restore this JVM property, as some tests change it
|
||||
System.setProperty(RowObjectFilterModel.SUB_FILTERING_DISABLED_PROPERTY,
|
||||
|
@ -88,8 +93,28 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
waitForTableModel(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestThreadedTableModelListener createListener() {
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
isDisposing = true;
|
||||
dispose();
|
||||
}
|
||||
|
||||
protected void buildFrame(GThreadedTablePanel<TestRowObject> tablePanel) {
|
||||
runSwing(() -> {
|
||||
frame = new DockingFrame("Threaded Table Test");
|
||||
frame.getContentPane().setLayout(new BorderLayout());
|
||||
frame.getContentPane().add(new JScrollPane(tablePanel));
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
protected void dispose() {
|
||||
close(frame);
|
||||
runSwing(threadedTablePanel::dispose);
|
||||
}
|
||||
|
||||
private TestThreadedTableModelListener createListener() {
|
||||
return new TestThreadedTableModelListener(model, recorder);
|
||||
}
|
||||
|
||||
|
@ -127,11 +152,11 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
|
||||
assertTableDoesNotContainValue(newRowIndex);
|
||||
|
||||
addItemToModel(newRowIndex);
|
||||
TestRowObject newItem = addItemToModel(newRowIndex);
|
||||
assertDidFilter();
|
||||
assertTableContainsValue(newRowIndex);
|
||||
|
||||
removeItemFromModel(newRowIndex);
|
||||
removeItemFromModel(newItem);
|
||||
assertDidFilter();
|
||||
assertTableDoesNotContainValue(newRowIndex);
|
||||
|
||||
|
@ -213,10 +238,57 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
assertRowCount(3); // matching values: two, ten, ten, ten
|
||||
|
||||
startsWithFilter("t");
|
||||
|
||||
// note: if the user is typing, no refilter will take place. But, since we are setting a
|
||||
// new filter in this test, instead of typing, a refilter is forced. This is why 4 items
|
||||
// pass through the filter instead of 0.
|
||||
assertNumberOfItemsPassedThroughFilter(4);
|
||||
assertRowCount(4); // matching values: two, ten, ten, ten
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubFilter_EscapeCharacters_StartsWithFilter() {
|
||||
|
||||
//
|
||||
// Test that sub filters correctly handle escape characters. Users may enter backslashes
|
||||
// when attempting to escape globbing characters (i.e., ? or *)
|
||||
//
|
||||
|
||||
TestRowObject ro = new TestRowObject("t?n", System.currentTimeMillis());
|
||||
model.addObject(ro);
|
||||
waitForTableModel(model);
|
||||
|
||||
startsWithFilter_AllowGlobbing("t");
|
||||
assertFilteredEntireModel();
|
||||
assertRowCount(5); // matching values: two, ten, ten, ten, t?n
|
||||
|
||||
// sub-filter
|
||||
startsWithFilter_AllowGlobbing("t\\"); // t\
|
||||
assertNumberOfItemsPassedThroughFilter(5);
|
||||
assertRowCount(0); // nothing matching a literal backslash
|
||||
|
||||
startsWithFilter_AllowGlobbing("t\\?");
|
||||
// sub-filter again
|
||||
// The previous filer was not used due to our the the code we have that checks for globbing
|
||||
// escape characters. But, the filter before that using just 't' is a valid parent of the
|
||||
// current filter, so that get used.
|
||||
assertNumberOfItemsPassedThroughFilter(5);
|
||||
assertRowCount(1); // matching values: t?n
|
||||
|
||||
// go backwards
|
||||
startsWithFilter_AllowGlobbing("t\\");
|
||||
assertNumberOfItemsPassedThroughFilter(5);
|
||||
assertRowCount(0); // nothing matching a literal backslash
|
||||
|
||||
startsWithFilter_AllowGlobbing("t");
|
||||
|
||||
// note: if the user is typing, no refilter will take place. But, since we are setting a
|
||||
// new filter in this test, instead of typing, a refilter is forced. This is why 5 items
|
||||
// pass through the filter instead of 0.
|
||||
assertNumberOfItemsPassedThroughFilter(5);
|
||||
assertRowCount(5); // matching values: two, ten, ten, ten, t?n
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubFilter_RoundTrip_RegexFilter() throws Exception {
|
||||
|
||||
|
@ -245,6 +317,10 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
assertRowCount(3); // matching values: two, ten, ten, ten
|
||||
|
||||
regexFilter("^t");
|
||||
|
||||
// note: if the user is typing, no refilter will take place. But, since we are setting a
|
||||
// new filter in this test, instead of typing, a refilter is forced. This is why 4 items
|
||||
// pass through the filter instead of 0.
|
||||
assertNumberOfItemsPassedThroughFilter(4);
|
||||
assertRowCount(4); // matching values: two, ten, ten, ten
|
||||
}
|
||||
|
@ -268,6 +344,10 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
|
||||
// jump from 'ten' to 't'--should still used the filtered data for 't'
|
||||
startsWithFilter("t");
|
||||
|
||||
// note: if the user is typing, no refilter will take place. But, since we are setting a
|
||||
// new filter in this test, instead of typing, a refilter is forced. This is why 4 items
|
||||
// pass through the filter instead of 0.
|
||||
assertNumberOfItemsPassedThroughFilter(4);
|
||||
assertRowCount(4); // matching values: two, ten, ten, ten
|
||||
}
|
||||
|
@ -334,7 +414,7 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
// 'isSubFilterOf'
|
||||
//
|
||||
|
||||
TableFilter<Long> customFilter = new EmptyCustomFilter();
|
||||
TableFilter<TestRowObject> customFilter = new EmptyCustomFilter();
|
||||
createCombinedStartsWithFilter("t", customFilter);
|
||||
assertFilteredEntireModel();
|
||||
assertRowCount(4); // matching values: two, ten, ten, ten
|
||||
|
@ -361,7 +441,7 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
// 'isSubFilterOf'
|
||||
//
|
||||
|
||||
TableFilter<Long> customFilter = new StringColumnContainsCustomFilter("t");
|
||||
TableFilter<TestRowObject> customFilter = new StringColumnContainsCustomFilter("t");
|
||||
createCombinedStartsWithFilter("t", customFilter);
|
||||
assertFilteredEntireModel();
|
||||
assertRowCount(4); // matching values (for both filters): two, ten, ten, ten
|
||||
|
@ -414,8 +494,10 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
|
||||
int fullCount = getRowCount();
|
||||
|
||||
// use filter to limit any new items added from passing
|
||||
Predicate<Long> predicate = l -> l < fullCount;
|
||||
Predicate<TestRowObject> predicate = ro -> {
|
||||
int index = model.getRowIndex(ro);
|
||||
return index >= 0; // < 0 means the row object is a new item not yet in the model
|
||||
};
|
||||
PredicateTableFilter noNewItemsPassFilter = new PredicateTableFilter(predicate);
|
||||
createCombinedFilterWithEmptyTextFilter(noNewItemsPassFilter);
|
||||
assertFilteredEntireModel();
|
||||
|
@ -474,22 +556,38 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
}
|
||||
|
||||
private void assertTableContainsValue(long expected) {
|
||||
List<Long> modelValues = getModelData();
|
||||
assertTrue("Value not in the model--filtered out? - Expected " + expected + "; found " +
|
||||
modelValues, modelValues.contains(expected));
|
||||
List<TestRowObject> modelValues = getModelData();
|
||||
for (TestRowObject ro : modelValues) {
|
||||
if (ro.getLongValue() == expected) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("Value not in the model--filtered out? - Expected " + expected + "; found " +
|
||||
modelValues);
|
||||
}
|
||||
|
||||
private void assertTableDoesNotContainValue(long expected) {
|
||||
List<Long> modelValues = getModelData();
|
||||
assertFalse("Value in the model--should not be there - Value " + expected + "; found " +
|
||||
modelValues, modelValues.contains(expected));
|
||||
List<TestRowObject> modelValues = getModelData();
|
||||
for (TestRowObject ro : modelValues) {
|
||||
if (ro.getLongValue() == expected) {
|
||||
fail("Value in the model--should not be there - Value " + expected + "; found " +
|
||||
modelValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getUnfilteredRowCount() {
|
||||
return runSwing(() -> model.getUnfilteredRowCount());
|
||||
}
|
||||
|
||||
private List<TestRowObject> getModelData() {
|
||||
return runSwing(() -> model.getModelData());
|
||||
}
|
||||
|
||||
private void filterOnRawColumnValue(long filterValue) throws Exception {
|
||||
|
||||
// the row objects are Long values that are 0-based one-up index values
|
||||
RowFilterTransformer<Long> transformer = value -> {
|
||||
List<String> result = Arrays.asList(Long.toString(value));
|
||||
RowFilterTransformer<TestRowObject> transformer = value -> {
|
||||
List<String> result = Arrays.asList(Long.toString(value.getLongValue()));
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -515,17 +613,25 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
filterOnStringsColumnValue(filterValue, TextFilterStrategy.STARTS_WITH);
|
||||
}
|
||||
|
||||
private void startsWithFilter_AllowGlobbing(String filterValue) {
|
||||
filterOnStringsColumnValue(filterValue, TextFilterStrategy.STARTS_WITH, true);
|
||||
}
|
||||
|
||||
private void containsFilter(String filterValue) {
|
||||
filterOnStringsColumnValue(filterValue, TextFilterStrategy.CONTAINS);
|
||||
}
|
||||
|
||||
private void filterOnStringsColumnValue(String filterValue, TextFilterStrategy filterStrategy) {
|
||||
filterOnStringsColumnValue(filterValue, filterStrategy, false);
|
||||
}
|
||||
|
||||
// the row objects are Long values that are 0-based one-up index values
|
||||
DefaultRowFilterTransformer<Long> transformer =
|
||||
private void filterOnStringsColumnValue(String filterValue, TextFilterStrategy filterStrategy,
|
||||
boolean allowGlobbing) {
|
||||
|
||||
DefaultRowFilterTransformer<TestRowObject> transformer =
|
||||
new DefaultRowFilterTransformer<>(model, table.getColumnModel());
|
||||
|
||||
FilterOptions options = new FilterOptions(filterStrategy, false, true, false);
|
||||
FilterOptions options = new FilterOptions(filterStrategy, allowGlobbing, true, false);
|
||||
TextFilterFactory textFactory = options.getTextFilterFactory();
|
||||
TextFilter textFilter = textFactory.getTextFilter(filterValue);
|
||||
|
||||
|
@ -538,16 +644,15 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
waitForSwing();
|
||||
}
|
||||
|
||||
private void createCombinedFilterWithEmptyTextFilter(TableFilter<Long> nonTextFilter) {
|
||||
private void createCombinedFilterWithEmptyTextFilter(TableFilter<TestRowObject> nonTextFilter) {
|
||||
|
||||
// the row objects are Long values that are 0-based one-up index values
|
||||
DefaultRowFilterTransformer<Long> transformer =
|
||||
DefaultRowFilterTransformer<TestRowObject> transformer =
|
||||
new DefaultRowFilterTransformer<>(model, table.getColumnModel());
|
||||
|
||||
TextFilter allPassesFilter = new EmptyTextFilter();
|
||||
spyFilter = new SpyTextFilter<>(allPassesFilter, transformer, recorder);
|
||||
|
||||
CombinedTableFilter<Long> combinedFilter =
|
||||
CombinedTableFilter<TestRowObject> combinedFilter =
|
||||
new CombinedTableFilter<>(spyFilter, nonTextFilter, null);
|
||||
|
||||
recorder.record("Before setting the new filter");
|
||||
|
@ -560,10 +665,9 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
}
|
||||
|
||||
private void createCombinedStartsWithFilter(String filterValue,
|
||||
TableFilter<Long> secondFilter) {
|
||||
TableFilter<TestRowObject> secondFilter) {
|
||||
|
||||
// the row objects are Long values that are 0-based one-up index values
|
||||
DefaultRowFilterTransformer<Long> transformer =
|
||||
DefaultRowFilterTransformer<TestRowObject> transformer =
|
||||
new DefaultRowFilterTransformer<>(model, table.getColumnModel());
|
||||
|
||||
TextFilterStrategy filterStrategy = TextFilterStrategy.STARTS_WITH;
|
||||
|
@ -573,7 +677,7 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
|
||||
spyFilter = new SpyTextFilter<>(textFilter, transformer, recorder);
|
||||
|
||||
CombinedTableFilter<Long> combinedFilter =
|
||||
CombinedTableFilter<TestRowObject> combinedFilter =
|
||||
new CombinedTableFilter<>(spyFilter, secondFilter, null);
|
||||
|
||||
recorder.record("Before setting the new filter");
|
||||
|
@ -586,8 +690,8 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
}
|
||||
|
||||
private void startsWithFilter_CaseInsensitive(String filterValue) {
|
||||
// the row objects are Long values that are 0-based one-up index values
|
||||
DefaultRowFilterTransformer<Long> transformer =
|
||||
|
||||
DefaultRowFilterTransformer<TestRowObject> transformer =
|
||||
new DefaultRowFilterTransformer<>(model, table.getColumnModel());
|
||||
|
||||
FilterOptions options =
|
||||
|
@ -609,40 +713,46 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
monitor.clearMessages();
|
||||
}
|
||||
|
||||
protected void assertDidNotFilter() {
|
||||
assertFalse("The table filtered data when it should not have", monitor.hasFilterMessage());
|
||||
}
|
||||
|
||||
protected void assertDidFilter() {
|
||||
private void assertDidFilter() {
|
||||
assertTrue("The table did not filter data when it should have", spyFilter.hasFiltered());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void record(String message) {
|
||||
recorder.record("Test - " + message);
|
||||
private void waitForNotBusy() {
|
||||
sleep(50);
|
||||
waitForCondition(() -> testTableModelListener.doneWork(),
|
||||
"Timed-out waiting for table model to update.");
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private int getRowCount() {
|
||||
return runSwing(() -> model.getRowCount());
|
||||
}
|
||||
|
||||
private TestRowObject addItemToModel(long l) {
|
||||
TestRowObject ro = new TestRowObject(String.valueOf(l), l);
|
||||
model.addObject(ro);
|
||||
waitForTableModel(model);
|
||||
return ro;
|
||||
}
|
||||
|
||||
private void removeItemFromModel(TestRowObject ro) {
|
||||
model.removeObject(ro);
|
||||
waitForTableModel(model);
|
||||
}
|
||||
|
||||
private void assertRowCount(int expectedCount) {
|
||||
int rowCount = model.getRowCount();
|
||||
assertThat("Have different number of table rows than expected after filtering", rowCount,
|
||||
is(expectedCount));
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private class RawRowValueTableColumn extends AbstractDynamicTableColumnStub<Long, Long> {
|
||||
private class EmptyCustomFilter implements TableFilter<TestRowObject> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Raw Row Value";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getValue(Long rowObject, Settings settings, ServiceProvider provider)
|
||||
throws IllegalArgumentException {
|
||||
return rowObject;
|
||||
}
|
||||
}
|
||||
|
||||
private class EmptyCustomFilter implements TableFilter<Long> {
|
||||
|
||||
@Override
|
||||
public boolean acceptsRow(Long rowObject) {
|
||||
public boolean acceptsRow(TestRowObject rowObject) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -653,9 +763,9 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
}
|
||||
}
|
||||
|
||||
private class StringColumnContainsCustomFilter implements TableFilter<Long> {
|
||||
private class StringColumnContainsCustomFilter implements TableFilter<TestRowObject> {
|
||||
|
||||
DefaultRowFilterTransformer<Long> transformer =
|
||||
DefaultRowFilterTransformer<TestRowObject> transformer =
|
||||
new DefaultRowFilterTransformer<>(model, table.getColumnModel());
|
||||
private String filterText;
|
||||
|
||||
|
@ -664,7 +774,7 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsRow(Long rowObject) {
|
||||
public boolean acceptsRow(TestRowObject rowObject) {
|
||||
|
||||
List<String> strings = transformer.transform(rowObject);
|
||||
for (String s : strings) {
|
||||
|
@ -702,10 +812,10 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
}
|
||||
}
|
||||
|
||||
private class AllPassesTableFilter implements TableFilter<Long> {
|
||||
private class AllPassesTableFilter implements TableFilter<TestRowObject> {
|
||||
|
||||
@Override
|
||||
public boolean acceptsRow(Long rowObject) {
|
||||
public boolean acceptsRow(TestRowObject rowObject) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -715,16 +825,16 @@ public class DefaultThreadedTableFilterTest extends AbstractThreadedTableTest {
|
|||
}
|
||||
}
|
||||
|
||||
private class PredicateTableFilter implements TableFilter<Long> {
|
||||
private class PredicateTableFilter implements TableFilter<TestRowObject> {
|
||||
|
||||
private Predicate<Long> predicate;
|
||||
private Predicate<TestRowObject> predicate;
|
||||
|
||||
PredicateTableFilter(Predicate<Long> predicate) {
|
||||
PredicateTableFilter(Predicate<TestRowObject> predicate) {
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsRow(Long rowObject) {
|
||||
public boolean acceptsRow(TestRowObject rowObject) {
|
||||
return predicate.test(rowObject);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.widgets.table.threaded;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TestRowObject {
|
||||
|
||||
private String s;
|
||||
private long l;
|
||||
|
||||
TestRowObject(String s, long l) {
|
||||
this.s = s;
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
public String getStringValue() {
|
||||
return s;
|
||||
}
|
||||
|
||||
public long getLongValue() {
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[string=" + s + ", long=" + l + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(l, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TestRowObject other = (TestRowObject) obj;
|
||||
return l == other.l && Objects.equals(s, other.s);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.widgets.table.threaded;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumnStub;
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.TestDummyServiceProvider;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class TestThreadedTableModel extends ThreadedTableModelStub<TestRowObject> {
|
||||
|
||||
// note: this data is derived from TestDataKeyModel, but can be changed
|
||||
private List<TestRowObject> startData = List.of(
|
||||
new TestRowObject("one", 0x0000000DFAA00C4FL),
|
||||
new TestRowObject("two", 0x00000001FD7CA6A6L),
|
||||
new TestRowObject("THREE", 0xFFFFFFF4D0EB4AB8L),
|
||||
new TestRowObject("Four", 0x0000000445246143L),
|
||||
new TestRowObject("FiVe", 0xFFFFFFF5696F1780L),
|
||||
new TestRowObject("sIx", 0x0000000685526E5DL),
|
||||
new TestRowObject("SeVEn", 0x00000009A1FD98EEL),
|
||||
new TestRowObject("EighT", 0x00000004AD2B1869L),
|
||||
new TestRowObject("NINE", 0x00000002928E64C8L),
|
||||
new TestRowObject("ten", 0x000000071CE1DDB2L),
|
||||
new TestRowObject("ten", 0x000000071CE1DDB3L),
|
||||
new TestRowObject("ten", 0x000000071CE1DDB4L));
|
||||
|
||||
public TestThreadedTableModel() {
|
||||
super("Test Data Key Model", new TestDummyServiceProvider(), null, false);
|
||||
}
|
||||
|
||||
public int getStartRowCount() {
|
||||
return startData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<TestRowObject> createTableColumnDescriptor() {
|
||||
|
||||
TableColumnDescriptor<TestRowObject> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(new ByteTableColumn());
|
||||
descriptor.addVisibleColumn(new LongTableColumn());
|
||||
descriptor.addVisibleColumn(new StringTableColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<TestRowObject> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
for (TestRowObject element : startData) {
|
||||
monitor.checkCancelled();
|
||||
accumulator.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
private class ByteTableColumn extends AbstractDynamicTableColumnStub<TestRowObject, Byte> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Byte";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte getValue(TestRowObject rowObject, Settings settings, ServiceProvider provider)
|
||||
throws IllegalArgumentException {
|
||||
return (byte) rowObject.getLongValue();
|
||||
}
|
||||
}
|
||||
|
||||
private class LongTableColumn extends AbstractDynamicTableColumnStub<TestRowObject, Long> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Long";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getValue(TestRowObject rowObject, Settings settings, ServiceProvider provider)
|
||||
throws IllegalArgumentException {
|
||||
return rowObject.getLongValue();
|
||||
}
|
||||
}
|
||||
|
||||
private class StringTableColumn extends AbstractDynamicTableColumnStub<TestRowObject, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "String";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(TestRowObject rowObject, Settings settings, ServiceProvider provider)
|
||||
throws IllegalArgumentException {
|
||||
return rowObject.getStringValue();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue