GP-2797 - Fixed the table column filtering to correctly match input data containing newline characters when using the 'Contains' string column filter.

This commit is contained in:
dragonmacher 2022-11-08 17:26:56 -05:00
parent a3c72f3859
commit 65142e127d
10 changed files with 149 additions and 31 deletions

View File

@ -293,7 +293,7 @@
</CENTER><BR>
<P>The <IMG alt="" src="images/view-filter.png"> button in lower right corner will bring up
<P>The <IMG alt="" src="images/filter_off.png"> button in lower right corner will bring up
the Column Filter Dialog.</P>
<BR>
<BR>

View File

@ -77,7 +77,7 @@ public abstract class StringColumnConstraint implements ColumnConstraint<String>
protected Pattern generateFindsPattern() {
String regexString = UserSearchUtils.createPatternString(patternString, true);
return Pattern.compile("(" + regexString + ")",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.DOTALL);
}
@Override

View File

@ -46,7 +46,6 @@ public class StringContainsColumnConstraint extends StringColumnConstraint {
@Override
protected Pattern generateMatchesPattern(String patternString) {
return UserSearchUtils.createContainsPattern(patternString.trim(), true,
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.DOTALL);
}
}

View File

@ -45,6 +45,6 @@ public class StringEndsWithColumnConstraint extends StringColumnConstraint {
@Override
protected Pattern generateMatchesPattern(String patternString) {
return UserSearchUtils.createEndsWithPattern(patternString, true,
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.DOTALL);
}
}

View File

@ -18,6 +18,8 @@ package docking.widgets.table.constraint;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import ghidra.util.UserSearchUtils;
/**
* String column constraint for matching column values if they match a full regular expression pattern.
*/
@ -26,7 +28,7 @@ public class StringMatchesColumnConstraint extends StringColumnConstraint {
* Constructor
*
* <P> This class is for users to enter true regular expression which is why it creates
* a pattern directly without using the UserSearchUtils
* a pattern directly without using the {@link UserSearchUtils}.
*
* @param spec the string to use to create a "matcher" pattern.
*/

View File

@ -15,6 +15,8 @@
*/
package docking.widgets.table.constraint;
import ghidra.util.UserSearchUtils;
/**
* String column constraint for matching column values if they do not match a full regular
* expression pattern.
@ -25,7 +27,7 @@ public class StringNotMatchesColumnConstraint extends StringMatchesColumnConstra
* Constructor
*
* <P> This class is for users to enter true regular expression which is why it creates
* a pattern directly without using the UserSearchUtils
* a pattern directly without using the {@link UserSearchUtils}
*
* @param spec the string to use to create a "matcher" pattern.
*/

View File

@ -46,6 +46,6 @@ public class StringStartsWithColumnConstraint extends StringColumnConstraint {
@Override
protected Pattern generateMatchesPattern(String patternString) {
return UserSearchUtils.createStartsWithPattern(patternString, true,
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.DOTALL);
}
}

View File

@ -227,7 +227,6 @@ public class AutocompletingStringConstraintEditor extends DataLoadingConstraintE
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
return sb.toString();
}

View File

@ -19,8 +19,8 @@ import static org.junit.Assert.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.Date;
import java.util.List;
import javax.swing.table.TableColumn;
@ -29,14 +29,9 @@ import org.junit.Test;
import docking.test.AbstractDockingTest;
import docking.widgets.table.*;
import docking.widgets.table.constraint.ColumnConstraint;
import docking.widgets.table.constraint.MappedColumnConstraint;
import docking.widgets.table.constraint.dialog.*;
import docking.widgets.table.constraint.provider.*;
import ghidra.framework.options.SaveState;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
@ -85,6 +80,18 @@ public class ColumnTableFilterTest extends AbstractDockingTest {
return new TableModelWrapper<>(testTableModel);
}
private RowObjectFilterModel<Integer> createTableModelWithNewlines() {
TestTableModel testTableModel = new TestTableModel();
testTableModel.addColumn("Name",
new String[] { "Hello World\n", "\nHello World", "Hello\nWorld", "Hello World" });
testTableModel.addColumn("Long ID",
new Long[] { 1000l, 2000l, 3000l });
return new TableModelWrapper<>(testTableModel);
}
private Date date(String dateString) {
try {
return DATE_FORMAT.parse(dateString);
@ -107,6 +114,27 @@ public class ColumnTableFilterTest extends AbstractDockingTest {
assertEquals("Chuck", tableModel.getValueAt(0, col));
}
@Test
public void testStringStartsWithColumnFilter_WithNewline() {
runSwing(() -> gTable.dispose());
tableModel = createTableModelWithNewlines();
gTable = new GTable(tableModel);
filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null);
addFirstFilter("Name", "Starts With", "Hello");
applyFilter();
assertEquals(3, tableModel.getRowCount());
int col = getColumn("Name");
assertEquals("Hello World\n", tableModel.getValueAt(0, col));
assertEquals("Hello\nWorld", tableModel.getValueAt(1, col));
assertEquals("Hello World", tableModel.getValueAt(2, col));
}
@Test
public void testStringDoesNotStartsWithColumnFilter() {
addFirstFilter("Name", "Does Not Start With", "C");
@ -136,6 +164,27 @@ public class ColumnTableFilterTest extends AbstractDockingTest {
assertEquals("Dave", tableModel.getValueAt(1, col));
}
@Test
public void testStringEndsWithColumnFilter_WithNewline() {
runSwing(() -> gTable.dispose());
tableModel = createTableModelWithNewlines();
gTable = new GTable(tableModel);
filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null);
addFirstFilter("Name", "Ends With", "World");
applyFilter();
assertEquals(3, tableModel.getRowCount());
int col = getColumn("Name");
assertEquals("\nHello World", tableModel.getValueAt(0, col));
assertEquals("Hello\nWorld", tableModel.getValueAt(1, col));
assertEquals("Hello World", tableModel.getValueAt(2, col));
}
@Test
public void testStringContainsColumnFilter() {
addFirstFilter("Name", "Contains", "l");
@ -149,6 +198,44 @@ public class ColumnTableFilterTest extends AbstractDockingTest {
assertEquals("Ellen", tableModel.getValueAt(1, col));
}
@Test
public void testStringContainsColumnFilter_WithNewline() {
runSwing(() -> gTable.dispose());
tableModel = createTableModelWithNewlines();
gTable = new GTable(tableModel);
filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null);
addFirstFilter("Name", "Contains", "Hello");
applyFilter();
assertEquals(4, tableModel.getRowCount());
int col = getColumn("Name");
assertEquals("Hello World\n", tableModel.getValueAt(0, col));
assertEquals("\nHello World", tableModel.getValueAt(1, col));
assertEquals("Hello\nWorld", tableModel.getValueAt(2, col));
assertEquals("Hello World", tableModel.getValueAt(3, col));
}
@Test
public void testStringNotContainsColumnFilter_WithNewline() {
runSwing(() -> gTable.dispose());
tableModel = createTableModelWithNewlines();
gTable = new GTable(tableModel);
filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null);
addFirstFilter("Name", "Does Not Contain", "Hello");
applyFilter();
assertEquals(0, tableModel.getRowCount());
}
@Test
public void testStringMatchesColumnFilter() {
addFirstFilter("Name", "Matches Regex", ".*l.*e.*");
@ -162,6 +249,49 @@ public class ColumnTableFilterTest extends AbstractDockingTest {
assertEquals("Ellen", tableModel.getValueAt(1, col));
}
@Test
public void testStringMatchesColumnFilter_WithNewline() {
runSwing(() -> gTable.dispose());
tableModel = createTableModelWithNewlines();
gTable = new GTable(tableModel);
filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null);
addFirstFilter("Name", "Matches Regex", ".*Hello\\sWorld.*");
applyFilter();
assertEquals(2, tableModel.getRowCount());
// only matches when newline not at the beginning or end, since DOTALL mode is not used
int col = getColumn("Name");
assertEquals("Hello\nWorld", tableModel.getValueAt(0, col));
assertEquals("Hello World", tableModel.getValueAt(1, col));
}
@Test
public void testStringMatchesColumnFilter_WithNewline_WithDotallMode() {
runSwing(() -> gTable.dispose());
tableModel = createTableModelWithNewlines();
gTable = new GTable(tableModel);
filterModel = new ColumnFilterDialogModel<>(tableModel, gTable.getColumnModel(), null);
addFirstFilter("Name", "Matches Regex", "(?s).*Hello\\sWorld.*");
applyFilter();
assertEquals(4, tableModel.getRowCount());
int col = getColumn("Name");
assertEquals("Hello World\n", tableModel.getValueAt(0, col));
assertEquals("\nHello World", tableModel.getValueAt(1, col));
assertEquals("Hello\nWorld", tableModel.getValueAt(2, col));
assertEquals("Hello World", tableModel.getValueAt(3, col));
}
@Test
public void testStringNotMatchesColumnFilter() {
addFirstFilter("Name", "Does Not Match Regex", ".*l.*e.*");
@ -683,20 +813,6 @@ public class ColumnTableFilterTest extends AbstractDockingTest {
return null;
}
@SuppressWarnings("unchecked")
private List<ColumnConstraint<?>> loadConstraints() {
List<ColumnConstraint<?>> list = new ArrayList<>();
list.addAll(new NumberColumnConstraintProvider().getColumnConstraints());
list.addAll(new StringColumnConstraintProvider().getColumnConstraints());
Collection<ColumnConstraint<?>> columnConstraints =
new DateColumnConstraintProvider().getColumnConstraints();
for (ColumnConstraint<?> c : columnConstraints) {
list.add(new MappedColumnConstraint<>(new DateColumnTypeMapper(),
(ColumnConstraint<LocalDate>) c));
}
return list;
}
private void addConstraints(String columnName, String[] constraintNames,
String[] constraintValues, boolean first) {

View File

@ -112,7 +112,7 @@ class TestTableModel implements RowObjectTableModel<Integer> {
@Override
public List<Integer> getModelData() {
List<Integer> rowObjects = new ArrayList<>();
for (int i = 0; i < getColumnCount(); i++) {
for (int i = 0; i < rowCount; i++) {
rowObjects.add(i);
}
return rowObjects;