GP-4410 - Version Tracking - Added support for deleting matches; Added table column filters

This commit is contained in:
dragonmacher 2024-07-18 13:54:26 -04:00
parent 76977bd514
commit 9f73d23ee4
36 changed files with 1335 additions and 699 deletions

View File

@ -26,6 +26,7 @@ project.ext.excludeFromParallelIntegrationTests = true
dependencies {
api project(":Base")
runtimeOnly project(":CodeCompare")
testImplementation project(path: ':Project', configuration: 'testArtifacts')
testImplementation project(path: ':SoftwareModeling', configuration: 'testArtifacts')

View File

@ -315,9 +315,56 @@
<P align="left"><A name="Clear_Match"></A>The <b>Clear Match</b> <IMG src="images/undo-apply.png" border="0">
action will reset the match to unaccepted and undo any applied markup.</P>
<P align="left"><A name="Remove_Match"></A>The <b>Remove Match</b> <IMG src="images/edit-delete.png" border="0">
action will remove a manually created match from the matches table.</P>
action will remove the selected match(es).</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/warning.png" alt="Note" border="0">
As of Ghidra 11.2, Version Tracking supports deleting matches. Any match that has
not been accepted can be deleted without confirmation. However, if you attempt to
delete an <B>accepted match</B> that is the <B>last match for an association</B>, then
you will be prompted to confirm your decision.
</P>
<P>
Generally, we suggest users should not delete accepted matches. The more matches that
are accepted, the better the Version Tracking results, since user choices affect
future match scores. Keeping accepted matches and the applied markup provides
future analysis with more corroborating details. Contrastingly, deleting accepted
matches while keeping applied markup will remove supporting evidence that the user has
already substantiated.
</P>
<BLOCKQUOTE>
<P><IMG src="help/shared/tip.png">An alternative to deleting matches is to
simply filter them out of the table once they have been applied. You can also tag
any matches you wish to ignore and then use the <A HREF="#VT_Advanced_Filters">
advanced filters</a> to hide any matches with those tags.
</P>
</BLOCKQUOTE>
<P>
It is important to understand what happens in Version Tracking when deleting a match.
You will have to make a decision before deleting whether you want to keep any changes
made to the destination program when you accepted a given match or whether you wish to
remove that markup. When deleting an accepted match:
</P>
<BLOCKQUOTE>
<P>
<U>To keep all applied markup</U>, simply delete the match and, when
prompted, choose <B>Delete Accepted Matches</B>. This choice will delete the match and
its markup items, but <B>any applied markup item content will remain in the destination
program.</B> Alternatively, when prompted, you can choose <B>Finish</B> which will
close the prompt dialog and will not delete the remaining accepted matches or markup.
<P>
<U>To remove all applied markup</U>, then you must first
<A HREF="#Clear_Match">clear</A> the match before executing the remove action. The clear
action will remove applied markup. After clearing the match, then you can remove
the match and no markup will remain in the destination program.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P align="left"><A name="Make_Selections"></A>The <b>Make Selections</b> <IMG src="Icons.MAKE_SELECTION_ICON" border="0">
action will create selections in the source and destination tools for all matches selected in the table.</P>
@ -400,79 +447,101 @@
<H2><A name="Match_Filters"></A>Match Filters</H2>
<BLOCKQUOTE>
<P align="left">The match table has an extensive assortment of filters. There
are several commonly used filter controls at the bottom of the table:
<ol>
<li><b>Text Filter</b> - allows you to filter based on any text in the table
</li>
<li><b>Score Filter</b> - allows you to filter on a range of scores. All scores
are between 0 and 1
</li>
<li><b>Confidence Filter</b> - allows you to filter a range of confidence values.
All confidence values will be greater than -9.999 and smaller than 9.999.
</li>
<li><b>Length Filter</b> - is used to filter out
functions that are smaller than some number
</li>
</ol>
</P>
<P>Finally, the <IMG src="images/view-filter.png" border="0"> will show the ancillary filters
available. The table below lists and describes the available filters. When an ancillary
filter is applied, the icon will change to <IMG src="images/lightbulb.png" border="0"> .
Further, the icon may occasionally flash as a reminder that there is a filter applied.</P>
<BR>
<TABLE border="1" width="90%">
<TR>
<TH nowrap>Filter Name</TH>
<TH>Description</TH>
</TR>
<TR>
<TD valign="top">Match Type</TD>
<TD valign="top">This filter allows the user to show only function or data matches.</TD>
</TR>
<TR>
<TD valign="top" nowrap>Association Status</TD>
<TD valign="top">This filter allows the user to show only matches whose assocation
has one of the included status types. A useful setting
for this filter is to turn off all but the <B>Available</B> status. This will cause the
table to act like a "To Do" list.</TD>
</TR>
<TR>
<TD valign="top">Symbol Type</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination labels are of one of the included symbol types.</TD>
</TR>
<TR>
<TD valign="top">Algorithms</TD>
<TD valign="top">This filter allows the user to show only matches that were generated
by one of the included types of correlating algorithms</TD>
</TR>
<TR>
<TD valign="top">Address Range</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination address is within the specified range.</TD>
</TR>
<TR>
<TD valign="top">Tags</TD>
<TD valign="top">This filter allows the user to show only matches whose tag is an
included tag.</TD>
</TR>
<BLOCKQUOTE>
<H3>Table Filters</H3>
</TABLE>
<BLOCKQUOTE>
<P align="left">The match table has an extensive assortment of filters. There
are several commonly used filter controls at the bottom of the table:
<ol>
<li><b>Text Filter</b> - allows you to filter based on any text in the table
</li>
<li><b>Score Filter</b> - allows you to filter on a range of scores. All scores
are between 0 and 1
</li>
<li><b>Confidence Filter</b> - allows you to filter a range of confidence values.
All confidence values will be greater than -9.999 and smaller than 9.999.
</li>
<li><b>Length Filter</b> - is used to filter out
functions that are smaller than some number
</li>
</ol>
</P>
</BLOCKQUOTE>
<A NAME="VT_Advanced_Filters"></A>
<H3>Advanced Filters</H3>
<BLOCKQUOTE>
<P>Finally, the <IMG src="icon.version.tracking.unfiltered" border="0"> will show the ancillary filters
available. The table below lists and describes the available filters. When an ancillary
filter is applied, the icon will change to <IMG src="icon.version.tracking.filtered" border="0"> .
Further, the icon may occasionally flash as a reminder that there is a filter applied.</P>
<BR>
<TABLE border="1" width="90%">
<TR>
<TH nowrap>Filter Name</TH>
<TH>Description</TH>
</TR>
<TR>
<TD valign="top">Match Type</TD>
<TD valign="top">This filter allows the user to show only function or data matches.</TD>
</TR>
<TR>
<TD valign="top" nowrap>Association Status</TD>
<TD valign="top">This filter allows the user to show only matches whose assocation
has one of the included status types. A useful setting
for this filter is to turn off all but the <B>Available</B> status. This will cause the
table to act like a "To Do" list.</TD>
</TR>
<TR>
<TD valign="top">Symbol Type</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination labels are of one of the included symbol types.</TD>
</TR>
<TR>
<TD valign="top">Algorithms</TD>
<TD valign="top">This filter allows the user to show only matches that were generated
by one of the included types of correlating algorithms</TD>
</TR>
<TR>
<TD valign="top">Address Range</TD>
<TD valign="top">This filter allows the user to show only matches whose source or
destination address is within the specified range.</TD>
</TR>
<TR>
<TD valign="top">Tags</TD>
<TD valign="top">This filter allows the user to show only matches whose tag is an
included tag.</TD>
</TR>
</TABLE>
</BLOCKQUOTE>
<H3>Table Column Filters</H3>
<BLOCKQUOTE>
The matches table also supports
<A HREF="help/topics/Trees/GhidraTreeFilter.html#Column_Filters">
Table Column Filters</A> for creating complex filters for individual table columns.
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE> <!-- end of top-level blockquote -->

View File

@ -21,8 +21,7 @@ import java.io.IOException;
import java.util.*;
import db.*;
import ghidra.feature.vt.api.impl.MarkupItemStorage;
import ghidra.feature.vt.api.impl.VTEvent;
import ghidra.feature.vt.api.impl.*;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.util.VTAssociationStatusException;
import ghidra.framework.data.OpenMode;
@ -138,33 +137,13 @@ public class AssociationDatabaseManager implements VTAssociationManager {
return createMarkupItemDB(markupItemStorage);
}
void removeMarkupItem(MarkupItemStorageDB appliedMarkupItemDB) {
// non-interface method; internal API use
public void removeStoredMarkupItems(List<MarkupItemImpl> impls) {
VTAssociationDB association = (VTAssociationDB) appliedMarkupItemDB.getAssociation();
validateAcceptedState(appliedMarkupItemDB, association);
try {
markupItemTableAdapter.removeMatchMarkupItemRecord(appliedMarkupItemDB.getKey());
}
catch (IOException e) {
session.dbError(e);
}
}
private void validateAcceptedState(MarkupItemStorageDB appliedItem,
VTAssociationDB association) {
//
// For any 'applied' markup item we assume that its association will be 'ACCEPTED'. The
// exception to this rule is when we have markup items in the database, but that are not
// applied (like when we change the destination address without applying)
//
VTAssociationStatus associationStatus = association.getStatus();
VTMarkupItemStatus status = appliedItem.getStatus();
if (status.isUnappliable()) {
if (associationStatus != ACCEPTED) {
throw new AssertException("Cannot have an applied markup item with an " +
"association that is not ACCEPTED");
for (MarkupItemImpl impl : impls) {
MarkupItemStorage storage = impl.getStorage();
if (storage instanceof MarkupItemStorageDB storageDb) {
removeMarkupRecord(storageDb.getKey());
}
}
}
@ -251,17 +230,31 @@ public class AssociationDatabaseManager implements VTAssociationManager {
}
void removeAssociation(VTAssociation association) {
VTAssociationDB existingAssociation = (VTAssociationDB) association;
long id = existingAssociation.getKey();
// Update the association status so that we update any blocked associations
VTAssociationDB associationDB = (VTAssociationDB) association;
VTAssociationStatus status = association.getStatus();
if (status == ACCEPTED) {
associationDB.setStatus(AVAILABLE);
associationDB.setInvalid();
unblockRelatedAssociations(associationDB);
for (AssociationHook hook : associationHooks) {
hook.associationCleared(associationDB);
}
}
VTAssociationDB associationDb = (VTAssociationDB) association;
long id = associationDb.getKey();
try {
associationDb.removeMarkupItems();
associationTableAdapter.removeAssociaiton(id);
session.setChanged(VTEvent.ASSOCIATION_REMOVED, existingAssociation, null);
session.setChanged(VTEvent.ASSOCIATION_REMOVED, associationDb, null);
}
catch (IOException e) {
session.dbError(e);
}
associationCache.delete(id);
existingAssociation.setInvalid();
associationDb.setInvalid();
}
@ -507,7 +500,7 @@ public class AssociationDatabaseManager implements VTAssociationManager {
throws VTAssociationStatusException {
if (association.hasAppliedMarkupItems()) {
throw new VTAssociationStatusException(
"VTMarkupItemManager contains applied " + "markup items");
"VTMarkupItemManager contains applied markup items");
}
}
@ -623,9 +616,9 @@ public class AssociationDatabaseManager implements VTAssociationManager {
associationHooks.remove(hook);
}
void removeMarkupRecord(DBRecord record) {
void removeMarkupRecord(long key) {
try {
markupItemTableAdapter.removeMatchMarkupItemRecord(record.getKey());
markupItemTableAdapter.removeMarkupItemRecord(key);
}
catch (IOException e) {
session.dbError(e);

View File

@ -135,7 +135,7 @@ public class MarkupItemStorageDB extends DatabaseObject implements MarkupItemSto
try {
MarkupItemStorage storage = new MarkupItemStorageImpl(getAssociation(), getMarkupType(),
getSourceAddress(), getDestinationAddress(), getDestinationAddressSource());
associationManager.removeMarkupRecord(record);
associationManager.removeMarkupRecord(record.getKey());
return storage;
}
finally {
@ -144,7 +144,8 @@ public class MarkupItemStorageDB extends DatabaseObject implements MarkupItemSto
}
@Override
public MarkupItemStorage setDestinationAddress(Address destinationAddress, String addressSource) {
public MarkupItemStorage setDestinationAddress(Address destinationAddress,
String addressSource) {
if (destinationAddress == null) {
destinationAddress = Address.NO_ADDRESS;
}

View File

@ -296,4 +296,8 @@ public class VTAssociationDB extends DatabaseObject implements VTAssociation {
public boolean hasAppliedMarkupItems() {
return markupManager.hasAppliedMarkupItems();
}
void removeMarkupItems() {
markupManager.removeMarkupItems();
}
}

View File

@ -60,7 +60,7 @@ public abstract class VTMatchMarkupItemTableDBAdapter {
public abstract RecordIterator getRecords() throws IOException;
public abstract void removeMatchMarkupItemRecord(long key) throws IOException;
public abstract void removeMarkupItemRecord(long key) throws IOException;
public abstract DBRecord getRecord(long key) throws IOException;
@ -70,5 +70,6 @@ public abstract class VTMatchMarkupItemTableDBAdapter {
public abstract int getRecordCount();
public abstract DBRecord createMarkupItemRecord(MarkupItemStorage markupItem) throws IOException;
public abstract DBRecord createMarkupItemRecord(MarkupItemStorage markupItem)
throws IOException;
}

View File

@ -15,14 +15,11 @@
*/
package ghidra.feature.vt.api.db;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.ADDRESS_SOURCE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.ASSOCIATION_KEY_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.DESTINATION_ADDRESS_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.MARKUP_TYPE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.ORIGINAL_DESTINATION_VALUE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.SOURCE_ADDRESS_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.SOURCE_VALUE_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.STATUS_COL;
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.*;
import java.io.IOException;
import db.*;
import ghidra.feature.vt.api.impl.MarkupItemStorage;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.api.markuptype.VTMarkupTypeFactory;
@ -34,10 +31,6 @@ import ghidra.program.model.listing.Program;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import db.*;
public class VTMatchMarkupItemTableDBAdapterV0 extends VTMatchMarkupItemTableDBAdapter {
private Table table;
@ -71,20 +64,20 @@ public class VTMatchMarkupItemTableDBAdapterV0 extends VTMatchMarkupItemTableDBA
record.setLongValue(ASSOCIATION_KEY_COL.column(), association.getKey());
record.setString(ADDRESS_SOURCE_COL.column(), markupItem.getDestinationAddressSource());
record.setLongValue(SOURCE_ADDRESS_COL.column(), getAddressID(sourceProgram,
markupItem.getSourceAddress()));
record.setLongValue(SOURCE_ADDRESS_COL.column(),
getAddressID(sourceProgram, markupItem.getSourceAddress()));
Address destinationAddress = markupItem.getDestinationAddress();
if (destinationAddress != null) {
record.setLongValue(DESTINATION_ADDRESS_COL.column(), getAddressID(destinationProgram,
markupItem.getDestinationAddress()));
record.setLongValue(DESTINATION_ADDRESS_COL.column(),
getAddressID(destinationProgram, markupItem.getDestinationAddress()));
}
record.setShortValue(MARKUP_TYPE_COL.column(),
(short) VTMarkupTypeFactory.getID(markupItem.getMarkupType()));
record.setString(SOURCE_VALUE_COL.column(), Stringable.getString(
markupItem.getSourceValue(), sourceProgram));
record.setString(ORIGINAL_DESTINATION_VALUE_COL.column(), Stringable.getString(
markupItem.getDestinationValue(), destinationProgram));
record.setString(SOURCE_VALUE_COL.column(),
Stringable.getString(markupItem.getSourceValue(), sourceProgram));
record.setString(ORIGINAL_DESTINATION_VALUE_COL.column(),
Stringable.getString(markupItem.getDestinationValue(), destinationProgram));
record.setByteValue(STATUS_COL.column(), (byte) markupItem.getStatus().ordinal());
table.putRecord(record);
@ -97,7 +90,7 @@ public class VTMatchMarkupItemTableDBAdapterV0 extends VTMatchMarkupItemTableDBA
}
@Override
public void removeMatchMarkupItemRecord(long key) throws IOException {
public void removeMarkupItemRecord(long key) throws IOException {
table.deleteRecord(key);
}

View File

@ -25,8 +25,6 @@ import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import db.*;
import ghidra.feature.vt.api.correlator.program.ImpliedMatchProgramCorrelator;
import ghidra.feature.vt.api.correlator.program.ManualMatchProgramCorrelator;
import ghidra.feature.vt.api.impl.*;
import ghidra.feature.vt.api.main.*;
import ghidra.framework.data.OpenMode;
@ -186,37 +184,47 @@ public class VTMatchSetDB extends DatabaseObject implements VTMatchSet {
@Override
public boolean removeMatch(VTMatch match) {
if (!(match instanceof VTMatchDB)) {
return false;
}
if (!match.getMatchSet().hasRemovableMatches()) {
return false;
}
VTMatchDB matchDB = (VTMatchDB) match;
if (!(match instanceof VTMatchDB matchDb)) {
// this should not be possible from the UI
throw new IllegalArgumentException("Can only remove matches saved to the database");
}
VTAssociation association = match.getAssociation();
// Remove the association if it was the only remaining match for that association.
AssociationDatabaseManager associationManager = session.getAssociationManagerDBM();
List<VTMatch> matches = session.getMatches(association);
if (matches.size() == 1 && association.getStatus() == VTAssociationStatus.ACCEPTED) {
return false; // can't remove the last match if the association is accepted
// This method prevents deleting the association if it is accepted, as it would cause
// the user to lose potentially valuable information without realizing it. To work
// around that issue when calling this method, the user can first un-accept the match.
return false;
}
// Remove the match record
deleteMatch(matchDb);
return true;
}
@Override
public void deleteMatch(VTMatch match) {
if (!(match instanceof VTMatchDB matchDb)) {
// this should not be possible from the UI
throw new IllegalArgumentException("Can only remove matches saved to the database");
}
VTAssociation association = match.getAssociation();
Address sourceAddress = association.getSourceAddress();
Address destinationAddress = association.getDestinationAddress();
try {
lock.acquire();
long matchKey = matchDB.getKey();
long matchKey = matchDb.getKey();
boolean deleted = matchTableAdapter.deleteRecord(matchKey);
if (deleted) {
matchCache.delete(matchKey);
if (matches.size() == 1) {
List<VTMatch> matches = session.getMatches(association);
if (matches.isEmpty()) {
// if last match, remove association
associationManager.removeAssociation(association);
AssociationDatabaseManager manager = session.getAssociationManagerDBM();
manager.removeAssociation(association);
}
}
}
@ -229,7 +237,6 @@ public class VTMatchSetDB extends DatabaseObject implements VTMatchSet {
DeletedMatch deletedMatch = new DeletedMatch(sourceAddress, destinationAddress);
session.setObjectChanged(VTEvent.MATCH_DELETED, match, deletedMatch, null);
return true;
}
@Override
@ -349,14 +356,6 @@ public class VTMatchSetDB extends DatabaseObject implements VTMatchSet {
}
}
@Override
public boolean hasRemovableMatches() {
VTProgramCorrelatorInfo info = getProgramCorrelatorInfo();
String correlatorClassName = info.getCorrelatorClassName();
return correlatorClassName.equals(ManualMatchProgramCorrelator.class.getName()) ||
correlatorClassName.equals(ImpliedMatchProgramCorrelator.class.getName());
}
@Override
public String toString() {
return "Match Set " + getID() + " - " + getMatchCount() + " matches [Correlator=" +

View File

@ -501,4 +501,8 @@ public class MarkupItemImpl implements VTMarkupItem {
newStatus);
}
// non-interface method
public MarkupItemStorage getStorage() {
return markupItemStorage;
}
}

View File

@ -15,14 +15,14 @@
*/
package ghidra.feature.vt.api.impl;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.feature.vt.api.db.*;
import ghidra.feature.vt.api.main.VTMarkupItem;
import ghidra.feature.vt.api.main.VTMarkupItemStatus;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import java.util.*;
public class MarkupItemManagerImpl {
@ -70,7 +70,7 @@ public class MarkupItemManagerImpl {
return Collections.unmodifiableList(markupItems);
}
protected List<VTMarkupItem> createMarkupItems(TaskMonitor monitor) throws CancelledException {
private List<VTMarkupItem> createMarkupItems(TaskMonitor monitor) throws CancelledException {
Collection<VTMarkupItem> generatedMarkupItems = getGeneratedMarkupItems(monitor);
Collection<VTMarkupItem> databaseMarkupItems = getStoredMarkupItems(monitor);
@ -104,18 +104,6 @@ public class MarkupItemManagerImpl {
return false;
}
private Collection<VTMarkupItem> getStoredMarkupItems(TaskMonitor monitor)
throws CancelledException {
AssociationDatabaseManager associationDBM = association.getAssociationManagerDB();
Collection<MarkupItemStorageDB> appliedMarkupItems =
associationDBM.getAppliedMarkupItems(monitor, association);
List<VTMarkupItem> list = new ArrayList<VTMarkupItem>();
for (MarkupItemStorageDB markupItemStorageDB : appliedMarkupItems) {
list.add(new MarkupItemImpl(markupItemStorageDB));
}
return list;
}
private List<VTMarkupItem> replaceGeneratedMarkupItemsWithDBMarkupItems(
Collection<VTMarkupItem> generatedMarkupItems,
Collection<VTMarkupItem> databaseMarkupItems) {
@ -142,8 +130,45 @@ public class MarkupItemManagerImpl {
markupItem.getSourceAddress().toString(true);
}
public void clearCache() {
// synchronized due to write of 'markupItems'
public synchronized void clearCache() {
markupItems = EMPTY_LIST;
}
private Collection<VTMarkupItem> getStoredMarkupItems(TaskMonitor monitor)
throws CancelledException {
AssociationDatabaseManager associationDBM = association.getAssociationManagerDB();
Collection<MarkupItemStorageDB> appliedMarkupItems =
associationDBM.getAppliedMarkupItems(monitor, association);
List<VTMarkupItem> list = new ArrayList<VTMarkupItem>();
for (MarkupItemStorageDB markupItemStorageDB : appliedMarkupItems) {
list.add(new MarkupItemImpl(markupItemStorageDB));
}
return list;
}
// synchronized to match getMarkupItems() so we do not have other clients loading items while
// we are processing them
public synchronized void removeMarkupItems() {
List<VTMarkupItem> items;
try {
items = getMarkupItems(TaskMonitor.DUMMY);
}
catch (CancelledException e) {
return; // can't happen with DUMMY
}
List<MarkupItemImpl> impls = items.stream()
.map(item -> (MarkupItemImpl) item)
.filter(impl -> impl.isStoredInDB())
.collect(Collectors.toList());
AssociationDatabaseManager associationDbm = association.getAssociationManagerDB();
associationDbm.removeStoredMarkupItems(impls);
// signal that markup item info has changed and must be reloaded when next needed
clearCache();
}
}

View File

@ -15,13 +15,11 @@
*/
package ghidra.feature.vt.api.impl;
import ghidra.feature.vt.api.correlator.program.ImpliedMatchProgramCorrelator;
import ghidra.feature.vt.api.correlator.program.ManualMatchProgramCorrelator;
import java.util.*;
import ghidra.feature.vt.api.main.*;
import ghidra.program.model.address.Address;
import java.util.*;
public class MatchSetImpl implements VTMatchSet {
private ProgramCorrelatorInfoFake correlatorInfo;
private final VTSession session;
@ -77,11 +75,8 @@ public class MatchSetImpl implements VTMatchSet {
}
@Override
public boolean hasRemovableMatches() {
VTProgramCorrelatorInfo info = getProgramCorrelatorInfo();
String correlatorClassName = info.getCorrelatorClassName();
return correlatorClassName.equals(ManualMatchProgramCorrelator.class.getName()) ||
correlatorClassName.equals(ImpliedMatchProgramCorrelator.class.getName());
public void deleteMatch(VTMatch match) {
throw new UnsupportedOperationException();
}
@Override

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +15,11 @@
*/
package ghidra.feature.vt.api.main;
import java.util.Collection;
import ghidra.feature.vt.api.impl.VTProgramCorrelatorInfo;
import ghidra.program.model.address.Address;
import java.util.Collection;
/**
* Interface for all the matches generated from a single program correlator run.
*
@ -57,14 +56,14 @@ public interface VTMatchSet {
/**
* Returns the number of matches contained in this match set.
* @return
* @return the number of matches contained in this match set.
*/
public int getMatchCount();
/**
* Returns a unique id for this match set. The ids are one-up numbers indicating the order this
* match set was generated in relation to other match sets in the VTSession.
* @return
* @return the id
*/
public int getID();
@ -72,7 +71,7 @@ public interface VTMatchSet {
* Returns a collection of all matches for the given association.
* @param association the association for which to search for matches.
* @return a collection of all matches for the given association.
* @see #getMatches(Address, Address, VTAssociationType)
* @see #getMatches(Address, Address)
*/
public Collection<VTMatch> getMatches(VTAssociation association);
@ -90,16 +89,51 @@ public interface VTMatchSet {
public Collection<VTMatch> getMatches(Address sourceAddress, Address destinationAddress);
/**
* Removes a match from this match set. Note that this operation is only supported for built-in
* match sets "Manual Matches" and "Implied Matches".
* Deletes the given match from this match set.
* <P>
* Note: deleting an <B>ACCEPTED</B> match removes potentially useful corroborating evidence
* from future correlation. Before deleting a match, consider instead filtering matches out of
* the UI that you are finished applying.
* <P>
* If this is the last match that shares the match's association, then the association will also
* be removed, along with any markup items in the database. <B>Any applied markup item data
* will not be changed.</B>
*
* @param match the match
*/
public void deleteMatch(VTMatch match);
/**
* Removes a match from this match set.
* <P>
* If this is the last match that shares the match's association, then the match will only be
* removed if the association is not accepted. In that case, no remove will take place and
* this method will return false.
* <P>
* Note: This method is deprecated. It unfortunately shares a very similar name with its
* replacement, {@link #deleteMatch(VTMatch)}. The replacement method will delete the match
* and the related association and markup items in the database, if the match is the last match
* to use that association. This deprecated method does not remove the remaining association or
* markup items. Historically, this method has been called after clearing the given match and
* its markup. Once this method has been deleted, clients will be responsible for managing the
* markup item state before calling {@link #deleteMatch(VTMatch)}.
*
* @param match the match to remove.
* @return true if the match was removed.
* @throws IllegalArgumentException if a non-database match is passed to this method
* @see #deleteMatch(VTMatch)
* @deprecated use {@link #deleteMatch(VTMatch)}
*/
@Deprecated(since = "11.2", forRemoval = true)
public boolean removeMatch(VTMatch match);
/**
* Returns true if this match set supports removing matches.
* @return true if this match set supports removing matches.
* Returns true
* @return true
* @deprecated this method now always returns true
*/
public boolean hasRemovableMatches();
@Deprecated(since = "11.2", forRemoval = true)
public default boolean hasRemovableMatches() {
return true;
}
}

View File

@ -41,7 +41,6 @@ public class RemoveMatchAction extends DockingAction {
super("Remove", VTPlugin.OWNER);
this.controller = controller;
// setToolBarData(new ToolBarData(ICON, MENU_GROUP));
setPopupMenuData(new MenuData(new String[] { "Remove Match" }, ICON, MENU_GROUP));
setEnabled(false);
setHelpLocation(new HelpLocation("VersionTrackingPlugin", "Remove_Match"));
@ -64,17 +63,7 @@ public class RemoveMatchAction extends DockingAction {
}
VTMatchContext matchContext = (VTMatchContext) context;
List<VTMatch> matches = matchContext.getSelectedMatches();
if (matches.size() == 0) {
return false;
}
if (!isRemovableMatch(matches.get(0))) {
return false; // It must be a single manual match.
}
return true;
}
private boolean isRemovableMatch(VTMatch vtMatch) {
return vtMatch.getMatchSet().hasRemovableMatches();
return !matches.isEmpty();
}
@Override

View File

@ -282,12 +282,6 @@ public abstract class AbstractAddressRangeFilter<T> extends AncillaryFilter<T>
fireStatusChanged(getFilterStatus());
}
@Override
public void clearFilter() {
lowerAddressRangeTextField.setText(MIN_ADDRESS_VALUE.toString());
upperAddressRangeTextField.setText(MAX_ADDRESS_VALUE.toString());
}
@Override
public JComponent getComponent() {
return component;

View File

@ -15,8 +15,7 @@
*/
package ghidra.feature.vt.gui.filters;
import static ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus.APPLIED;
import static ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus.NONE;
import static ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus.*;
import java.awt.Container;
import java.awt.LayoutManager;
@ -151,13 +150,6 @@ public abstract class CheckBoxBasedAncillaryFilter<T> extends AncillaryFilter<T>
return FilterShortcutState.REQUIRES_CHECK;
}
@Override
public void clearFilter() {
for (CheckBoxInfo<T> info : checkBoxInfos) {
info.setSelected(true);
}
}
@Override
public FilterState getFilterState() {
FilterState state = new FilterState(this);

View File

@ -46,8 +46,6 @@ public abstract class Filter<T> {
public abstract FilterEditingStatus getFilterStatus();
public abstract void clearFilter();
public abstract JComponent getComponent();
public void dispose() {

View File

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,12 +17,13 @@ package ghidra.feature.vt.gui.filters;
public interface FilterDialogModel<T> {
public void addFilter( Filter<T> filter );
public void forceRefilter();
/**
* Will be called when the visibility of the dialog using this model has changed
*/
public void dialogVisibilityChanged( boolean isVisible );
public void addFilter(Filter<T> filter);
public void forceRefilter();
/**
* Will be called when the visibility of the dialog using this model has changed
* @param isVisible true if visible
*/
public void dialogVisibilityChanged(boolean isVisible);
}

View File

@ -243,12 +243,6 @@ public class TagFilter extends AncillaryFilter<VTMatch> {
excludedTags = getTagsFromText(tagText);
}
@Override
public void clearFilter() {
excludedTags.clear();
excludedTagsLabel.setText(ALL_TAGS_INCLUDED);
}
@Override
public JComponent getComponent() {
return component;

View File

@ -150,13 +150,14 @@ public class VTPlugin extends Plugin {
private void addCustomPlugins() {
List<String> names = new ArrayList<>(List.of("ghidra.features.codecompare.plugin"));
List<String> names =
new ArrayList<>(List.of("ghidra.features.codecompare.plugin.FunctionComparisonPlugin"));
List<Plugin> plugins = tool.getManagedPlugins();
Set<String> existingNames =
plugins.stream().map(c -> c.getName()).collect(Collectors.toSet());
// Note: we check to see if the plugins we want to add have already been added to the tool.
// We should not needed to do this, but once the tool has been saved with the plugins added,
// We should not need to do this, but once the tool has been saved with the plugins added,
// they will get added again the next time the tool is loaded. Adding this check here seems
// easier than modifying the default to file to load the plugins, since the amount of xml
// required for that is non-trivial.

View File

@ -127,12 +127,6 @@ public abstract class AbstractDoubleRangeFilter<T> extends Filter<T>
return component;
}
@Override
public void clearFilter() {
lowerBoundField.setText(minValue.toString());
upperBoundField.setText(maxValue.toString());
}
@Override
public FilterEditingStatus getFilterStatus() {
FilterEditingStatus lowerStatus = lowerBoundField.getFilterStatus();

View File

@ -89,11 +89,6 @@ public class LengthFilter extends Filter<VTMatch> {
return component;
}
@Override
public void clearFilter() {
textField.setText(DEFAULT_FILTER_VALUE.toString());
}
@Override
public FilterEditingStatus getFilterStatus() {
return textField.getFilterStatus();

View File

@ -34,6 +34,8 @@ import javax.swing.table.*;
import docking.*;
import docking.action.builder.ActionBuilder;
import docking.widgets.table.*;
import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
import docking.widgets.table.columnfilter.ColumnFilterManager;
import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService;
@ -80,6 +82,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
private AncillaryFilterDialogComponentProvider<VTMatch> ancillaryFilterDialog;
private JButton ancillaryFilterButton;
private ColumnFilterManager<VTMatch> columnFilterManager;
private VTColumnFilter vtColumnFilter;
private FilterIconFlashTimer<VTMatch> iconTimer;
private Set<Filter<VTMatch>> filters = new HashSet<>();
@ -386,8 +390,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
JPanel innerPanel = new JPanel(new HorizontalLayout(4));
innerPanel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
JComponent nameFilterPanel = createTextFilterPanel();
parentPanel.add(nameFilterPanel, BorderLayout.CENTER);
JComponent textFilterPanel = createTextFilterPanel();
parentPanel.add(textFilterPanel, BorderLayout.CENTER);
parentPanel.add(innerPanel, BorderLayout.EAST);
JComponent scoreFilterPanel = createScoreFilterPanel();
@ -409,13 +413,33 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
helpService.registerHelp(parentPanel, filterHelpLocation);
helpService.registerHelp(ancillaryFilterButton, filterHelpLocation);
JButton columnFilterButton = createColumnFilterButton();
innerPanel.add(columnFilterButton);
innerPanel.add(ancillaryFilterButton);
return parentPanel;
}
private JButton createColumnFilterButton() {
String preferenceKey =
matchesTable.getPreferenceKey() + ColumnFilterManager.FILTER_EXTENSION;
columnFilterManager = new ColumnFilterManager<VTMatch>(matchesTable, matchesTableModel,
preferenceKey, this::updateColumnFilter);
vtColumnFilter = new VTColumnFilter(columnFilterManager.getCurrentFilter());
addFilter(vtColumnFilter);
return columnFilterManager.getConfigureButton();
}
private void updateColumnFilter() {
vtColumnFilter.setFilter(columnFilterManager.getCurrentFilter());
refilter();
}
private JComponent createTextFilterPanel() {
// MatchNameFilter nameFilterPanel = new MatchNameFilter(controller, matchesTable);
AllTextFilter<VTMatch> allTextFilter =
new AllTextFilter<>(controller, matchesTable, matchesTableModel);
allTextFilter.setName(TEXT_FILTER_NAME);
@ -506,6 +530,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
}
ancillaryFilterDialog.dispose();
columnFilterManager.dispose();
}
@Override
@ -1069,4 +1095,73 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
return list;
}
}
private class VTColumnFilter extends Filter<VTMatch> {
private ColumnBasedTableFilter<VTMatch> columnFilter;
VTColumnFilter(ColumnBasedTableFilter<VTMatch> columnFilter) {
this.columnFilter = columnFilter;
}
void setFilter(ColumnBasedTableFilter<VTMatch> columnFilter) {
this.columnFilter = columnFilter;
}
@Override
public boolean passesFilter(VTMatch t) {
if (columnFilter == null) {
return true;
}
return columnFilter.acceptsRow(t);
}
@Override
public FilterEditingStatus getFilterStatus() {
if (columnFilter == null || columnFilter.isEmpty()) {
return FilterEditingStatus.NONE;
}
return FilterEditingStatus.APPLIED;
}
@Override
public JComponent getComponent() {
// This filter is configured outside of the VT filter API
throw new UnsupportedOperationException();
}
@Override
public FilterShortcutState getFilterShortcutState() {
return FilterShortcutState.REQUIRES_CHECK;
}
@Override
public Filter<VTMatch> createCopy() {
return this; // does not currently support copying; should not be needed
}
@Override
public void readConfigState(SaveState saveState) {
// handled by the column filter manager
}
@Override
public void writeConfigState(SaveState saveState) {
// handled by the column filter manager
}
@Override
public boolean isSubFilterOf(Filter<VTMatch> otherFilter) {
if (columnFilter == null) {
return false;
}
if (otherFilter instanceof VTColumnFilter otherColumnFilter) {
return columnFilter.isSubFilterOf(otherColumnFilter.columnFilter);
}
return false;
}
}
}

View File

@ -15,11 +15,13 @@
*/
package ghidra.feature.vt.gui.task;
import java.util.List;
import java.util.*;
import docking.widgets.OptionDialog;
import ghidra.feature.vt.api.db.VTMatchSetDB;
import ghidra.feature.vt.api.main.VTMatch;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -38,26 +40,75 @@ public class RemoveMatchTask extends VtTask {
return true;
}
private boolean removeMatches(TaskMonitor monitor) throws CancelledException {
private void removeMatches(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Removing matches");
monitor.initialize(matches.size());
boolean failed = false;
for (VTMatch match : matches) {
int n = matches.size();
monitor.initialize(n);
//
// First remove all matches that will not require user prompting (those that are not
// accepted or they are not the last match for a shared association).
//
List<VTMatch> list = new ArrayList<>(matches); // create a mutable list
Iterator<VTMatch> it = list.iterator();
while (it.hasNext()) {
monitor.checkCancelled();
VTMatch match = it.next();
VTMatchSetDB matchSet = (VTMatchSetDB) match.getMatchSet();
boolean matchRemoved = matchSet.removeMatch(match);
if (!matchRemoved) {
failed = true;
if (matchSet.removeMatch(match)) {
it.remove();
}
monitor.incrementProgress(1);
}
monitor.setProgress(matches.size());
if (failed) {
reportError("One or more of your matches could not be removed." +
"\nNote: You can't remove a match if it is currently accepted.");
if (list.isEmpty()) {
return;
}
//
// Now we have to ask the user if they wish to remove applied matches.
//
int delta = n - list.size();
//@formatter:off
String message = """
Deleted %d of %d matches.
The remaining %d matches are ACCEPTED. Do you wish to delete these matches and
leave any applied destination program markup in place?
(Press F1 to see more help details)
""".formatted(delta, n, list.size());
//@formatter:on
RemoveMatchDialog dialog = new RemoveMatchDialog(message);
if (!dialog.promptToDelete()) {
return;
}
it = list.iterator();
while (it.hasNext()) {
monitor.checkCancelled();
VTMatch match = it.next();
VTMatchSetDB matchSet = (VTMatchSetDB) match.getMatchSet();
matchSet.deleteMatch(match);
it.remove();
monitor.incrementProgress(1);
}
return true;
}
private class RemoveMatchDialog extends OptionDialog {
RemoveMatchDialog(String message) {
super("Delete ACCEPTED Matches?", message, "Delete Accepted Matches", "Finish",
OptionDialog.QUESTION_MESSAGE, null, false);
setHelpLocation(new HelpLocation("VersionTrackingPlugin", "Remove_Match"));
}
boolean promptToDelete() {
int choice = super.show();
return choice == OptionDialog.OPTION_ONE; // "Delete Accepted Matches"
}
}
}

View File

@ -125,11 +125,6 @@ public abstract class AbstractTextFilter<T> extends Filter<T> {
return component;
}
@Override
public void clearFilter() {
textField.setText(defaultValue);
}
@Override
public FilterEditingStatus getFilterStatus() {
return textField.getFilterStatus();

View File

@ -115,7 +115,7 @@ public class ImpliedMatchUtils {
VTMatchSet impliedMatchSet = session.getImpliedMatchSet();
for (VTMatch vtMatch : matches) {
if (vtMatch.getMatchSet() == impliedMatchSet) {
impliedMatchSet.removeMatch(vtMatch);
impliedMatchSet.deleteMatch(vtMatch);
}
}
}
@ -123,8 +123,7 @@ public class ImpliedMatchUtils {
/**
* Method for finding version tracking implied matches given an accepted matched
* function. Each referenced data and function that exist in equivalent sections
* of the matched source and destination functions will added to the current
* version tracking session as an implied match.
* of the matched source and destination functions will be returned in the given set.
*
* @param sourceFunction The matched function from the source program
* @param destinationFunction The matched function from the destination program

View File

@ -18,7 +18,7 @@ package ghidra.feature.vt.api;
import static ghidra.feature.vt.db.VTTestUtils.*;
import static org.junit.Assert.*;
import java.util.*;
import java.util.Arrays;
import org.junit.*;
@ -27,9 +27,8 @@ import ghidra.feature.vt.api.main.VTAssociationStatus;
import ghidra.feature.vt.api.main.VTMatch;
import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.task.AcceptMatchTask;
import ghidra.feature.vt.gui.task.VtTask;
import ghidra.feature.vt.gui.util.VTOptionDefines;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB;
@ -39,10 +38,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.test.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
@ -53,13 +49,8 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
private ProgramDB sourceProgram;
private ProgramDB destinationProgram;
private VTPlugin plugin;
private DomainObjectListenerRecorder eventRecorder = new DomainObjectListenerRecorder();
private Options options;
public VTMatchAcceptTest() {
super();
}
@Before
public void setUp() throws Exception {
@ -70,7 +61,6 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
ClassicSampleX86ProgramBuilder destinationBuilder = new ClassicSampleX86ProgramBuilder();
destinationProgram = destinationBuilder.getProgram();
destinationProgram.addListener(eventRecorder);
tool = env.getTool();
@ -84,29 +74,21 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
runSwing(() -> controller.openVersionTrackingSession(session));
options = controller.getOptions();
options.setBoolean(VTOptionDefines.AUTO_CREATE_IMPLIED_MATCH, false);
options.setBoolean(VTOptionDefines.APPLY_FUNCTION_NAME_ON_ACCEPT, false);
options.setBoolean(VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT, false);
}
@After
public void tearDown() throws Exception {
waitForBusyTool(tool);
destinationProgram.flushEvents();
waitForSwing();
env.dispose();
}
@Test
public void testAcceptWithApplyDataLabels() throws Exception {
//
// BTW this test exposes a bug because the hook that runs when you apply data on accept was.
// in a side effect, causing the destination address to be set. When the hook was changed to
// not set the destination address, the accept task was not setting the destination address
// as it should.
// This test exposes a bug because the hook that runs when you apply data on accept was
// exhibiting a side effect, causing the destination address to be set. When the hook was
// changed to not set the destination address, the accept task was not setting the
// destination address as it should.
//
options.setBoolean(VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT, true);
@ -147,11 +129,9 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
}
}
private void runTask(Task task) throws CancelledException {
task.run(TaskMonitor.DUMMY);
destinationProgram.flushEvents();
waitForSwing();
private void runTask(VtTask task) {
controller.runVTTask(task);
waitForProgram(destinationProgram);
}
private Data setData(DataType dataType, int dtLength, Address address, Program program)
@ -170,14 +150,4 @@ public class VTMatchAcceptTest extends AbstractGhidraHeadedIntegrationTest {
}
return data;
}
private class DomainObjectListenerRecorder implements DomainObjectListener {
List<DomainObjectChangedEvent> events = new ArrayList<DomainObjectChangedEvent>();
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
events.add(ev);
}
}
}

View File

@ -69,7 +69,6 @@ public class VTMatchApplyTest extends AbstractGhidraHeadedIntegrationTest {
private ProgramDB destinationProgram;
private VTPlugin plugin;
// TODO: debug
private DomainObjectListenerRecorder eventRecorder = new DomainObjectListenerRecorder();
@Before

View File

@ -0,0 +1,354 @@
/* ###
* 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 ghidra.feature.vt.api;
import static ghidra.feature.vt.db.VTTestUtils.*;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.*;
import docking.DialogComponentProvider;
import ghidra.feature.vt.api.db.VTSessionDB;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.db.DummyTestProgramCorrelator;
import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.task.*;
import ghidra.feature.vt.gui.util.VTOptionDefines;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.test.*;
public class VTMatchRemoveTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private VTController controller;
private VTPlugin plugin;
private VTSessionDB session;
private ProgramDB srcProgram;
private ProgramDB destProgram;
@Before
public void setUp() throws Exception {
env = new TestEnv();
ClassicSampleX86ProgramBuilder sourceBuilder = new ClassicSampleX86ProgramBuilder();
srcProgram = sourceBuilder.getProgram();
ClassicSampleX86ProgramBuilder destinationBuilder = new ClassicSampleX86ProgramBuilder();
destProgram = destinationBuilder.getProgram();
tool = env.getTool();
tool.addPlugin(VTPlugin.class.getName());
plugin = getPlugin(tool, VTPlugin.class);
controller = new VTControllerImpl(plugin);
session = new VTSessionDB(testName.getMethodName() + " - Test Match Set Manager",
srcProgram, destProgram, this);
runSwing(() -> controller.openVersionTrackingSession(session));
}
@After
public void tearDown() throws Exception {
env.dispose();
}
@Test
public void testRemoveMatch_UnaccpetedMatch() throws Exception {
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match, false);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertNoLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_AccpetedMatch() throws Exception {
/*
Test:
- create and apply a match
- remove the match
- leave the applied markup after match removal
*/
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
setApplyDataLabelOnAccept();
accept(match);
assertAcceptedAndLabelApplied(match, labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_Accepted_MultipleMatchesForAssociation() throws Exception {
/*
Test:
- create multiple matches for the same association
- apply one match
- remove the applied match
- leave the applied markup after match removal
*This tests control flow that avoids execution when the match being removed is the last
match for an association.
*/
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithMultipleMatchesToSameAssociation(srcAddr, destAddr);
setApplyDataLabelOnAccept();
accept(match);
assertAcceptedAndLabelApplied(match, labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match, false);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_RejectedMatch() throws Exception {
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
setApplyDataLabelOnAccept();
reject(match);
assertNoLabelApplied(labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
remove(match, false);
assertMatchRemoved(matchSet, srcAddr, destAddr);
assertNoLabelApplied(labelName, destAddr);
}
@Test
public void testRemoveMatch_AccpetedMatch_ChooseNotToDelete() throws Exception {
/*
Test:
- create and apply a match
- remove the match, but cancel at dialog prompt
- match should still be valid; markup should still be applied
*/
Address srcAddr = addr("0x0100808c", srcProgram);
Address destAddr = addr("0x0100808c", destProgram);
setDataOnPrograms(srcAddr, destAddr);
String labelName = "Bob";
addLabel(labelName, srcAddr, srcProgram);
VTMatch match = createMatchSetWithOneDataMatch(session, srcAddr, destAddr);
setApplyDataLabelOnAccept();
accept(match);
assertAcceptedAndLabelApplied(match, labelName, destAddr);
VTMatchSet matchSet = match.getMatchSet();
startRemoveThenCancel(match);
assertMatchNotRemoved(matchSet, srcAddr, destAddr);
assertLabelApplied(labelName, destAddr);
}
//=================================================================================================
// Private Methods
//=================================================================================================
private void remove(VTMatch match) {
remove(match, true);
}
private void remove(VTMatch match, boolean expectPrompt) {
RemoveMatchTask task = new RemoveMatchTask(session, List.of(match));
AtomicBoolean finished = runTaskLater(task); // this task is blocking, so run later and wait
if (expectPrompt) {
DialogComponentProvider removeDialog =
waitForDialogComponent("Delete ACCEPTED Matches?");
pressButtonByText(removeDialog, "Delete Accepted Matches");
}
// let the task finish processing after pressing the button
waitFor(finished);
waitForProgram(destProgram);
}
private void startRemoveThenCancel(VTMatch match) {
RemoveMatchTask task = new RemoveMatchTask(session, List.of(match));
AtomicBoolean finished = runTaskLater(task); // this task is blocking, so run later and wait
DialogComponentProvider removeDialog = waitForDialogComponent("Delete ACCEPTED Matches?");
pressButtonByText(removeDialog, "Finish");
// let the task finish processing after pressing the button
waitFor(finished);
waitForProgram(destProgram);
}
private void assertLabelApplied(String labelName, Address addr) {
assertEquals(labelName, getSymbol(destProgram, addr).getName());
}
private void assertNoLabelApplied(String labelName, Address addr) {
Symbol symbol = getSymbol(destProgram, addr);
if (symbol == null) {
return; // no label; expected
}
assertNotEquals(labelName, symbol.getName());
}
private void assertMatchRemoved(VTMatchSet matchSet, Address srcAddr, Address destAddr) {
Collection<VTMatch> matches = matchSet.getMatches(srcAddr, destAddr);
assertTrue(matches.isEmpty());
}
private void assertMatchNotRemoved(VTMatchSet matchSet, Address srcAddr, Address destAddr) {
Collection<VTMatch> matches = matchSet.getMatches(srcAddr, destAddr);
assertFalse(matches.isEmpty());
}
private void assertAcceptedAndLabelApplied(VTMatch match, String labelName, Address addr) {
VTAssociationStatus status = match.getAssociation().getStatus();
assertEquals(VTAssociationStatus.ACCEPTED, status);
assertEquals(labelName, getSymbol(destProgram, addr).getName());
}
private Symbol getSymbol(Program p, Address addr) {
return p.getSymbolTable().getPrimarySymbol(addr);
}
private void setApplyDataLabelOnAccept() {
ToolOptions options = controller.getOptions();
options.setBoolean(VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT, true);
}
private void accept(VTMatch match) throws Exception {
AcceptMatchTask task = new AcceptMatchTask(controller, List.of(match));
runTask(task);
}
private void reject(VTMatch match) throws Exception {
RejectMatchTask task = new RejectMatchTask(session, List.of(match));
runTask(task);
}
private void setDataOnPrograms(Address srcAddr, Address destAddr) {
DataType srcDt = new DWordDataType();
DataType destDt1 = new StringDataType();
DataType destDt2 = new WordDataType();
setData(srcDt, 4, srcAddr, srcProgram);
setData(destDt1, 2, destAddr, destProgram);
setData(destDt2, 2, destAddr.add(2), destProgram);
}
private Symbol addLabel(String name, Address address, Program program) {
return tx(program, () -> {
SymbolTable symbolTable = program.getSymbolTable();
return symbolTable.createLabel(address, name, SourceType.USER_DEFINED);
});
}
private Data setData(DataType dataType, int length, Address address, Program program) {
return tx(program, () -> {
Listing listing = program.getListing();
return listing.createData(address, dataType, length);
});
}
private AtomicBoolean runTaskLater(VtTask task) {
AtomicBoolean finishedFlag = new AtomicBoolean();
runSwingLater(() -> {
controller.runVTTask(task);
finishedFlag.set(true);
});
waitForSwing();
return finishedFlag;
}
private void runTask(VtTask task) {
controller.runVTTask(task);
waitForProgram(destProgram);
}
private VTMatch createMatchSetWithMultipleMatchesToSameAssociation(Address srcAddr,
Address destAddr) throws Exception {
int txId = 0;
try {
txId = session.startTransaction("Test Create Data Match Set");
VTMatchInfo info = createRandomMatch(srcAddr, destAddr, session);
info.setAssociationType(VTAssociationType.DATA);
VTMatchSet matchSet =
session.createMatchSet(createProgramCorrelator(srcProgram, destProgram));
VTMatch firstMatch = matchSet.addMatch(info);
// create a second match, match set and correlator, all tied to the given association
DummyTestProgramCorrelator pc2 =
(DummyTestProgramCorrelator) createProgramCorrelator(srcProgram, destProgram);
pc2.setName("Correlator Two");
VTMatchSet ms2 = session.createMatchSet(pc2);
ms2.addMatch(createRandomMatch(srcAddr, destAddr, session));
return firstMatch;
}
finally {
session.endTransaction(txId, true);
}
}
}

View File

@ -27,6 +27,7 @@ import ghidra.util.task.TaskMonitor;
public class DummyTestProgramCorrelator extends VTAbstractProgramCorrelator {
private String name = "DummyTestProgramCorrelator";
private int matchCount = 1;
public DummyTestProgramCorrelator() {
@ -84,8 +85,12 @@ public class DummyTestProgramCorrelator extends VTAbstractProgramCorrelator {
}
}
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return "DummyTestProgramCorrelator";
return name;
}
}

View File

@ -119,6 +119,7 @@ public class VTDomainObjectEventsTest extends VTBaseTestCase {
assertEquals(VTEvent.MATCH_ADDED, events.get(0).getEventType());
}
@SuppressWarnings("removal") // ignore the warning until removeMatch() is removed
@Test
public void testEventsForRemovingLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
@ -134,6 +135,22 @@ public class VTDomainObjectEventsTest extends VTBaseTestCase {
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@Test
public void testEventsForDeletingLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
clearEvents();
VTMatchInfo matchInfo = VTTestUtils.createRandomMatch(null);
VTMatch match = manualMatchSet.addMatch(matchInfo);
clearEvents();
manualMatchSet.deleteMatch(match);
assertEventCount(2);
assertEquals(VTEvent.ASSOCIATION_REMOVED, events.get(0).getEventType());
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@SuppressWarnings("removal") // ignore the warning until removeMatch() is removed
@Test
public void testEventsForRemovingNonLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
@ -149,6 +166,21 @@ public class VTDomainObjectEventsTest extends VTBaseTestCase {
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@Test
public void testEventsForDeletingNonLastMatchForAssociation() {
VTMatchSet manualMatchSet = db.getManualMatchSet();
clearEvents();
VTMatchInfo matchInfo = VTTestUtils.createRandomMatch(null);
VTMatch match = manualMatchSet.addMatch(matchInfo);
clearEvents();
manualMatchSet.deleteMatch(match);
assertEventCount(2);
assertEquals(VTEvent.ASSOCIATION_REMOVED, events.get(0).getEventType());
assertEquals(VTEvent.MATCH_DELETED, events.get(1).getEventType());
}
@Test
public void testEventsForRejectingMatch() throws VTAssociationStatusException {
VTMatchSet matchSet = createMatchSet();

View File

@ -24,29 +24,21 @@ import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.*;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import org.jdom.Element;
import docking.DockingWindowManager;
import docking.menu.*;
import docking.widgets.EmptyBorderButton;
import docking.widgets.EventTrigger;
import docking.widgets.filter.*;
import docking.widgets.label.GDLabel;
import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
import docking.widgets.table.columnfilter.ColumnFilterSaveManager;
import docking.widgets.table.constraint.dialog.ColumnFilterDialog;
import generic.theme.GIcon;
import docking.widgets.table.columnfilter.ColumnFilterManager;
import ghidra.framework.options.PreferenceState;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
import help.HelpService;
import resources.Icons;
import utilities.util.reflection.ReflectionUtilities;
import utility.function.Callback;
@ -112,37 +104,26 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
public static final String FILTER_TEXTFIELD_NAME = "filter.panel.textfield";
private static final String FILTER_STATE = "FILTER_STATE";
private static final String FILTER_EXTENSION = ".FilterExtension";
private static final Icon FILTER_ON_ICON = new GIcon("icon.widget.filterpanel.filter.on");
private static final Icon FILTER_OFF_ICON = new GIcon("icon.widget.filterpanel.filter.off");
private static final Icon APPLY_FILTER_ICON = Icons.OPEN_FOLDER_ICON;
private static final Icon CLEAR_FILTER_ICON = Icons.DELETE_ICON;
private JTable table;
private RowObjectFilterModel<ROW_OBJECT> textFilterModel;
private RowObjectFilterModel<ROW_OBJECT> rowObjectFilterModel;
private JLabel searchLabel;
private FilterTextField filterField;
private FilterListener filterListener = new GTableFilterListener();
private WeakSet<Callback> listeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
private FilterOptions filterOptions = new FilterOptions();
private TableTextFilterFactory<ROW_OBJECT> filterFactory =
new DefaultTableTextFilterFactory<>(filterOptions);
private RowFilterTransformer<ROW_OBJECT> transformer;
private TableFilter<ROW_OBJECT> secondaryTableFilter;
private ColumnBasedTableFilter<ROW_OBJECT> columnTableFilter;
private List<ColumnBasedTableFilter<ROW_OBJECT>> savedFilters = new ArrayList<>();
private EmptyBorderButton filterStateButton;
private ColumnFilterManager<ROW_OBJECT> columnFilterManager;
private String uniquePreferenceKey;
private MultiStateDockingAction<ColumnBasedTableFilter<ROW_OBJECT>> columnFilterAction;
private ColumnFilterDialog<ROW_OBJECT> columnFilterDialog;
private ColumnBasedTableFilter<ROW_OBJECT> lastUsedColumnFilter;
private SwingUpdateManager updateManager = new SwingUpdateManager(250, 1000, () -> {
private SwingUpdateManager filterUpdater = new SwingUpdateManager(250, 1000, () -> {
String text = filterField.getText();
TableFilter<ROW_OBJECT> tableFilter = filterFactory.getTableFilter(text, transformer);
@ -151,8 +132,9 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
// result of a filter, the table does not know this and may update the wrong row data.
table.editingCanceled(null);
textFilterModel.setTableFilter(
getCombinedTableFilter(secondaryTableFilter, tableFilter, columnTableFilter));
ColumnBasedTableFilter<ROW_OBJECT> columnFilter = columnFilterManager.getCurrentFilter();
rowObjectFilterModel.setTableFilter(
getCombinedTableFilter(secondaryTableFilter, tableFilter, columnFilter));
});
/** I'm a field so that my weak reference won't go away */
@ -174,12 +156,12 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
@Override
public void columnRemoved(TableColumnModelEvent e) {
updateTableContents();
filterUpdater.updateLater();
}
@Override
public void columnAdded(TableColumnModelEvent e) {
updateTableContents();
filterUpdater.updateLater();
}
};
@ -231,13 +213,16 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
String filterLabel) {
this.table = table;
buildPanel(filterLabel);
uniquePreferenceKey = createUniqueFilterPreferenceKey(table);
transformer = new DefaultRowFilterTransformer<>(tableModel, table.getColumnModel());
textFilterModel = installTableModel(tableModel);
rowObjectFilterModel = installTableModel(tableModel);
columnFilterManager = new ColumnFilterManager<ROW_OBJECT>(table, rowObjectFilterModel,
getPreferenceKey(), filterUpdater::updateLater);
buildPanel(filterLabel);
TableColumnModel columnModel = table.getColumnModel();
columnModel.addColumnModelListener(columnModelListener);
@ -246,12 +231,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
table.addPropertyChangeListener(badProgrammingPropertyChangeListener);
DockingWindowManager.registerComponentLoadedListener(this,
(windowManager, provider) -> initialize(windowManager));
}
private void initialize(DockingWindowManager windowManager) {
loadFilterPreference(windowManager);
initializeSavedFilters();
(windowManager, provider) -> loadFilterPreference(windowManager));
}
private void loadFilterPreference(DockingWindowManager dockingWindowManager) {
@ -283,7 +263,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
if (xmlElement != null) {
this.filterOptions = FilterOptions.restoreFromXML(xmlElement);
updateFilterFactory();
updateTableContents();
filterUpdater.updateLater();
}
}
@ -326,18 +306,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @param newFilter the ColumnTableFilter to use for filtering this table.
*/
public void setColumnTableFilter(ColumnBasedTableFilter<ROW_OBJECT> newFilter) {
if (Objects.equals(newFilter, this.columnTableFilter)) {
return;
}
if (columnTableFilter != null && !columnTableFilter.isSaved()) {
lastUsedColumnFilter = columnTableFilter;
}
columnTableFilter = newFilter;
updateTableContents();
updateColumnFilterButton();
if (columnFilterDialog != null) {
columnFilterDialog.filterChanged(newFilter);
}
columnFilterManager.setFilter(newFilter);
}
/**
@ -351,7 +320,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
*/
public void setFilterRowTransformer(RowFilterTransformer<ROW_OBJECT> transformer) {
this.transformer = transformer;
updateTableContents();
filterUpdater.updateLater();
}
/**
@ -362,7 +331,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
*/
public void setSecondaryFilter(TableFilter<ROW_OBJECT> tableFilter) {
this.secondaryTableFilter = tableFilter;
updateTableContents();
filterUpdater.updateLater();
}
/**
@ -373,7 +342,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
public void setFilterOptions(FilterOptions filterOptions) {
this.filterOptions = filterOptions;
updateFilterFactory();
updateTableContents();
filterUpdater.updateLater();
doSaveState();
}
@ -394,7 +363,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
add(buildFilterStateButton());
if (isTableColumnFilterableModel()) {
add(Box.createHorizontalStrut(5));
add(buildColumnFilterStateButton());
add(columnFilterManager.getConfigureButton());
}
HelpService helpService = DockingWindowManager.getHelpService();
@ -425,107 +394,6 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
return table.getModel() instanceof RowObjectFilterModel;
}
@SuppressWarnings("unchecked")
private JComponent buildColumnFilterStateButton() {
RowObjectFilterModel<ROW_OBJECT> tableModel =
(RowObjectFilterModel<ROW_OBJECT>) table.getModel();
columnFilterAction =
new NonToolbarMultiStateAction<>("Column Filter", "GTableFilterPanel") {
@Override
public void actionStateChanged(
ActionState<ColumnBasedTableFilter<ROW_OBJECT>> newActionState,
EventTrigger trigger) {
if (trigger != EventTrigger.GUI_ACTION) {
return;
}
ColumnFilterActionState state = (ColumnFilterActionState) newActionState;
state.performAction();
}
@Override
protected void actionPerformed() {
showFilterDialog(tableModel);
}
};
HelpLocation helpLocation = new HelpLocation("Trees", "Column_Filters");
columnFilterAction.setHelpLocation(helpLocation);
updateFilterFactory();
updateColumnFilterButton();
JButton button = columnFilterAction.createButton();
DockingWindowManager.getHelpService().registerHelp(button, helpLocation);
return button;
}
private void initializeSavedFilters() {
TableModel model = table.getModel();
if (!(model instanceof GDynamicColumnTableModel)) {
return;
}
@SuppressWarnings("unchecked")
GDynamicColumnTableModel<ROW_OBJECT, ?> dynamicModel =
(GDynamicColumnTableModel<ROW_OBJECT, ?>) model;
ColumnFilterSaveManager<ROW_OBJECT> saveManager =
new ColumnFilterSaveManager<>(this, table, dynamicModel, dynamicModel.getDataSource());
savedFilters = saveManager.getSavedFilters();
Collections.reverse(savedFilters);
updateColumnFilterButton();
}
private void updateColumnFilterButton() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = getActionStates();
columnFilterAction.setActionStates(list);
}
private List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> getActionStates() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = new ArrayList<>();
if (columnTableFilter == null) {
list.add(new CreateFilterActionState());
}
else {
list.add(new EditFilterActionState(columnTableFilter));
list.add(new ClearFilterActionState());
}
if (lastUsedColumnFilter != null) {
list.add(new ApplyLastUsedActionState(lastUsedColumnFilter));
}
for (ColumnBasedTableFilter<ROW_OBJECT> filter : savedFilters) {
list.add(new ApplyFilterActionState(filter));
}
return list;
}
private void showFilterDialog(RowObjectFilterModel<ROW_OBJECT> tableModel) {
if (columnFilterDialog == null) {
if (ColumnFilterDialog.hasFilterableColumns(table, tableModel)) {
DockingWindowManager dockingWindowManager = DockingWindowManager.getInstance(table);
loadFilterPreference(dockingWindowManager);
columnFilterDialog = new ColumnFilterDialog<>(this, table, tableModel);
}
else {
Msg.showError(this, this, "Column Filter Error",
"This table contains no filterable columns!");
return;
}
}
columnFilterDialog.setCloseCallback(() -> {
doSaveState();
updateFilterFactory();
columnFilterDialog = null;
});
DockingWindowManager.showDialog(GTableFilterPanel.this, columnFilterDialog);
}
private void updateFilterFactory() {
filterStateButton.setIcon(filterOptions.getFilterStateIcon());
filterStateButton.setToolTipText(filterOptions.getFilterDescription());
@ -587,17 +455,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
public RowObjectFilterModel<ROW_OBJECT> getTableFilterModel() {
return textFilterModel;
}
/** Convenience method to refilter the table's contents */
private void updateTableContents() {
updateManager.updateLater();
notifyFilterChanged();
}
private void notifyFilterChanged() {
listeners.forEach(callback -> callback.call());
return rowObjectFilterModel;
}
public void dispose() {
@ -609,13 +467,11 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
columnModel.removeColumnModelListener(columnModelListener);
columnModelListener = null;
if (columnFilterDialog != null) {
columnFilterDialog.dispose();
}
columnFilterManager.dispose();
table.removePropertyChangeListener(badProgrammingPropertyChangeListener);
updateManager.dispose();
filterUpdater.dispose();
if (table instanceof GTable) {
((GTable) table).dispose();
}
@ -696,7 +552,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
return viewRow;
}
return textFilterModel.getModelRow(viewRow);
return rowObjectFilterModel.getModelRow(viewRow);
}
/**
@ -710,7 +566,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @return the row in the table for the given model row.
*/
public int getViewRow(int modelRow) {
return textFilterModel.getViewRow(modelRow);
return rowObjectFilterModel.getViewRow(modelRow);
}
/**
@ -720,7 +576,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @return the row object matching the given index
*/
public ROW_OBJECT getRowObject(int viewRow) {
ROW_OBJECT rowObject = textFilterModel.getRowObject(viewRow);
ROW_OBJECT rowObject = rowObjectFilterModel.getRowObject(viewRow);
return rowObject;
}
@ -736,7 +592,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
return;
}
int viewRow = textFilterModel.getViewIndex(t);
int viewRow = rowObjectFilterModel.getViewIndex(t);
if (viewRow >= 0) {
table.setRowSelectionInterval(viewRow, viewRow);
scrollToSelectedRow();
@ -785,7 +641,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
if (row < 0) {
return null;
}
return textFilterModel.getRowObject(row);
return rowObjectFilterModel.getRowObject(row);
}
/**
@ -801,7 +657,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
List<ROW_OBJECT> list = new ArrayList<>(rows.length);
for (int row : rows) {
list.add(textFilterModel.getRowObject(row));
list.add(rowObjectFilterModel.getRowObject(row));
}
return list;
}
@ -814,7 +670,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
* @return true if in the view
*/
public boolean isInView(ROW_OBJECT o) {
int rowIndex = textFilterModel.getRowIndex(o);
int rowIndex = rowObjectFilterModel.getRowIndex(o);
return rowIndex >= 0;
}
@ -823,11 +679,48 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
public int getRowCount() {
return textFilterModel.getRowCount();
return rowObjectFilterModel.getRowCount();
}
public int getUnfilteredRowCount() {
return textFilterModel.getUnfilteredRowCount();
return rowObjectFilterModel.getUnfilteredRowCount();
}
/**
* Generates a key used to store user filter configuration state. You can override this
* method to generate unique keys yourself. You are required to override this method if
* you create multiple versions of a filter panel from the same place in your code, as
* multiple instances created in the same place will cause them all to share the same key and
* thus to have the same filter settings when they are created initially.
* <p>
* As an example, consider a plugin that creates <code>n</code> providers. If each provider uses
* a filter panel, then each provider will share the same filter settings when that provider
* is created. If this is not what you want, then you need to override this method to
* generate a unique key for each provider.
*
* @param jTable the table
* @return a key used to store user filter configuration state.
*/
public String createUniqueFilterPreferenceKey(JTable jTable) {
return generateFilterPreferenceKey(jTable, FILTER_EXTENSION);
}
/**
* Returns the ColumnTableFilter that has been set on this GTableFilterPanel or null if there
* is none.
*
* @return the ColumnTableFilter that has been set.
*/
public ColumnBasedTableFilter<ROW_OBJECT> getColumnTableFilter() {
return columnFilterManager.getCurrentFilter();
}
/**
* Return a unique key that can be used to store preferences for this table.
* @return a unique key that can be used to store preferences for this table.
*/
public String getPreferenceKey() {
return uniquePreferenceKey;
}
//==================================================================================================
@ -900,7 +793,7 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
private TableModelEvent translateEventForFilter(TableModelEvent event) {
int rowCount = textFilterModel.getUnfilteredRowCount();
int rowCount = rowObjectFilterModel.getUnfilteredRowCount();
if (rowCount == 0) {
return event; // nothing to translate--no data
}
@ -915,14 +808,14 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
if (firstRow == 0 && lastRow == rowCount - 1) {
firstRow = 0;
lastRow = Math.max(0, textFilterModel.getRowCount() - 1);
lastRow = Math.max(0, rowObjectFilterModel.getRowCount() - 1);
}
else {
// translate to the filtered view (from the wrapped model's full universe)
firstRow = getViewRow(firstRow);
lastRow = getViewRow(lastRow);
}
return new TableModelEvent(textFilterModel, firstRow, lastRow, event.getColumn(),
return new TableModelEvent(rowObjectFilterModel, firstRow, lastRow, event.getColumn(),
event.getType());
}
}
@ -937,8 +830,8 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
isUpdatingModel = true;
if (textFilterModel instanceof WrappingTableModel) {
WrappingTableModel tableModelWrapper = (WrappingTableModel) textFilterModel;
if (rowObjectFilterModel instanceof WrappingTableModel) {
WrappingTableModel tableModelWrapper = (WrappingTableModel) rowObjectFilterModel;
tableModelWrapper.wrappedModelChangedFromTableChangedEvent();
}
filterField.alert();
@ -947,75 +840,16 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
}
private class GTableFilterListener implements FilterListener {
@Override
public void filterChanged(String text) {
updateTableContents();
filterUpdater.updateLater();
}
}
/**
* Generates a key used to store user filter configuration state. You can override this
* method to generate unique keys yourself. You are required to override this method if
* you create multiple versions of a filter panel from the same place in your code, as
* multiple instances created in the same place will cause them all to share the same key and
* thus to have the same filter settings when they are created initially.
* <p>
* As an example, consider a plugin that creates <code>n</code> providers. If each provider uses
* a filter panel, then each provider will share the same filter settings when that provider
* is created. If this is not what you want, then you need to override this method to
* generate a unique key for each provider.
*
* @param jTable the table
* @return a key used to store user filter configuration state.
*/
public String createUniqueFilterPreferenceKey(JTable jTable) {
return generateFilterPreferenceKey(jTable, FILTER_EXTENSION);
}
/**
* Returns the ColumnTableFilter that has been set on this GTableFilterPanel or null if there
* is none.
*
* @return the ColumnTableFilter that has been set.
*/
public ColumnBasedTableFilter<ROW_OBJECT> getColumnTableFilter() {
return columnTableFilter;
}
/**
* Return a unique key that can be used to store preferences for this table.
* @return a unique key that can be used to store preferences for this table.
*/
public String getPreferenceKey() {
return uniquePreferenceKey;
}
/**
* Updates the "quick filter" multistate button.
* @param filter the filter to add or remove.
* @param add if true, the filter is added to the quick list. Otherwise, it is removed.
*/
public void updateSavedFilters(ColumnBasedTableFilter<ROW_OBJECT> filter, boolean add) {
if (add) {
ArrayList<ColumnBasedTableFilter<ROW_OBJECT>> list = new ArrayList<>();
list.add(filter);
list.addAll(savedFilters);
savedFilters = list;
if (filter.isEquivalent(columnTableFilter)) {
setColumnTableFilter(filter);
}
}
else {
savedFilters.remove(filter);
}
updateColumnFilterButton();
}
//==================================================================================================
// Static Methods
//==================================================================================================
private static String generateFilterPreferenceKey(JTable jTable, String extension) {
if (jTable instanceof GTable) {
@ -1039,78 +873,4 @@ public class GTableFilterPanel<ROW_OBJECT> extends JPanel {
String clientName = filteredTrace[0].getClassName();
return clientName;
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private abstract class ColumnFilterActionState
extends ActionState<ColumnBasedTableFilter<ROW_OBJECT>> {
ColumnFilterActionState(String name, Icon icon, ColumnBasedTableFilter<ROW_OBJECT> filter) {
super(name, icon, filter);
}
abstract void performAction();
}
String getFilterName(ColumnBasedTableFilter<ROW_OBJECT> filter) {
String filterName = filter.getName();
return filterName == null ? "Unsaved" : filterName;
}
private class ClearFilterActionState extends ColumnFilterActionState {
public ClearFilterActionState() {
super("Clear Filter", CLEAR_FILTER_ICON, null);
}
@Override
void performAction() {
setColumnTableFilter(null);
}
}
private class CreateFilterActionState extends ColumnFilterActionState {
public CreateFilterActionState() {
super("Create Column Filter", FILTER_OFF_ICON, null);
}
@Override
void performAction() {
showFilterDialog(textFilterModel);
}
}
private class EditFilterActionState extends ColumnFilterActionState {
public EditFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Edit: " + getFilterName(filter), FILTER_ON_ICON, filter);
}
@Override
void performAction() {
showFilterDialog(textFilterModel);
}
}
private class ApplyFilterActionState extends ColumnFilterActionState {
public ApplyFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply: " + getFilterName(filter), APPLY_FILTER_ICON, filter);
}
@Override
void performAction() {
setColumnTableFilter(getUserData());
}
}
private class ApplyLastUsedActionState extends ColumnFilterActionState {
public ApplyLastUsedActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply Last Unsaved", FILTER_ON_ICON, filter);
}
@Override
void performAction() {
setColumnTableFilter(getUserData());
}
}
}

View File

@ -0,0 +1,298 @@
/* ###
* 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.columnfilter;
import java.util.*;
import javax.swing.*;
import javax.swing.table.TableModel;
import docking.DockingWindowManager;
import docking.menu.*;
import docking.widgets.EventTrigger;
import docking.widgets.table.GDynamicColumnTableModel;
import docking.widgets.table.RowObjectFilterModel;
import docking.widgets.table.constraint.dialog.ColumnFilterDialog;
import generic.theme.GIcon;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import resources.Icons;
import utility.function.Callback;
/**
* A class that manages column filters for a table. This includes creating the UI elements that
* allow users to build filters, as well as a means to save and restore filters.
*
* @param <ROW_OBJECT> the row type
*/
public class ColumnFilterManager<ROW_OBJECT> {
public static final String FILTER_EXTENSION = ".FilterExtension";
public static final String FILTER_TEXTFIELD_NAME = "filter.panel.textfield";
private static final Icon FILTER_ON_ICON = new GIcon("icon.widget.filterpanel.filter.on");
private static final Icon FILTER_OFF_ICON = new GIcon("icon.widget.filterpanel.filter.off");
private static final Icon APPLY_FILTER_ICON = Icons.OPEN_FOLDER_ICON;
private static final Icon CLEAR_FILTER_ICON = Icons.DELETE_ICON;
private MultiStateDockingAction<ColumnBasedTableFilter<ROW_OBJECT>> columnFilterAction;
private JButton configureButton;
private ColumnFilterDialog<ROW_OBJECT> columnFilterDialog;
private ColumnBasedTableFilter<ROW_OBJECT> lastUsedFilter;
private ColumnBasedTableFilter<ROW_OBJECT> currentFilter;
private List<ColumnBasedTableFilter<ROW_OBJECT>> savedFilters = new ArrayList<>();
private JTable table;
private RowObjectFilterModel<ROW_OBJECT> rowObjectFilterModel;
private String preferenceKey;
private Callback filterChangedCallback;
public ColumnFilterManager(JTable table, RowObjectFilterModel<ROW_OBJECT> rowObjectFilterModel,
String preferenceKey, Callback filterChangedCallback) {
this.table = Objects.requireNonNull(table);
this.rowObjectFilterModel = Objects.requireNonNull(rowObjectFilterModel);
this.preferenceKey = Objects.requireNonNull(preferenceKey);
this.filterChangedCallback = Objects.requireNonNull(filterChangedCallback);
configureButton = buildColumnFilterStateButton();
DockingWindowManager.registerComponentLoadedListener(table,
(windowManager, provider) -> initializeSavedFilters());
}
private void initializeSavedFilters() {
TableModel model = table.getModel();
if (!(model instanceof GDynamicColumnTableModel)) {
return;
}
@SuppressWarnings("unchecked")
GDynamicColumnTableModel<ROW_OBJECT, ?> dynamicModel =
(GDynamicColumnTableModel<ROW_OBJECT, ?>) model;
ColumnFilterSaveManager<ROW_OBJECT> saveManager = new ColumnFilterSaveManager<>(
preferenceKey, table, dynamicModel, dynamicModel.getDataSource());
savedFilters = saveManager.getSavedFilters();
Collections.reverse(savedFilters);
updateColumnFilterButton();
}
public ColumnBasedTableFilter<ROW_OBJECT> getCurrentFilter() {
return currentFilter;
}
public JButton getConfigureButton() {
return configureButton;
}
public String getPreferenceKey() {
return preferenceKey;
}
public void setFilter(ColumnBasedTableFilter<ROW_OBJECT> newFilter) {
if (Objects.equals(newFilter, this.currentFilter)) {
return;
}
if (currentFilter != null && !currentFilter.isSaved()) {
lastUsedFilter = currentFilter;
}
currentFilter = newFilter;
updateColumnFilterButton();
if (columnFilterDialog != null) {
columnFilterDialog.filterChanged(newFilter);
}
filterChangedCallback.call();
}
public void updateSavedFilters(ColumnBasedTableFilter<ROW_OBJECT> filter, boolean add) {
if (add) {
ArrayList<ColumnBasedTableFilter<ROW_OBJECT>> list = new ArrayList<>();
list.add(filter);
list.addAll(savedFilters);
savedFilters = list;
if (filter.isEquivalent(currentFilter)) {
setFilter(filter);
}
}
else {
savedFilters.remove(filter);
}
updateColumnFilterButton();
filterChangedCallback.call();
}
public void dispose() {
if (columnFilterDialog != null) {
columnFilterDialog.dispose();
columnFilterDialog = null;
}
filterChangedCallback = Callback.dummy();
}
private JButton buildColumnFilterStateButton() {
columnFilterAction =
new NonToolbarMultiStateAction<>("Column Filter", "GTableFilterPanel") {
@Override
public void actionStateChanged(
ActionState<ColumnBasedTableFilter<ROW_OBJECT>> newActionState,
EventTrigger trigger) {
if (trigger != EventTrigger.GUI_ACTION) {
return;
}
ColumnFilterActionState state = (ColumnFilterActionState) newActionState;
state.performAction();
}
@Override
protected void actionPerformed() {
showFilterDialog(rowObjectFilterModel);
}
};
HelpLocation helpLocation = new HelpLocation("Trees", "Column_Filters");
columnFilterAction.setHelpLocation(helpLocation);
updateColumnFilterButton();
JButton button = columnFilterAction.createButton();
DockingWindowManager.getHelpService().registerHelp(button, helpLocation);
return button;
}
private void updateColumnFilterButton() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = getActionStates();
columnFilterAction.setActionStates(list);
}
private List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> getActionStates() {
List<ActionState<ColumnBasedTableFilter<ROW_OBJECT>>> list = new ArrayList<>();
if (currentFilter == null) {
list.add(new CreateFilterActionState());
}
else {
list.add(new EditFilterActionState(currentFilter));
list.add(new ClearFilterActionState());
}
if (lastUsedFilter != null) {
list.add(new ApplyLastUsedActionState(lastUsedFilter));
}
for (ColumnBasedTableFilter<ROW_OBJECT> filter : savedFilters) {
list.add(new ApplyFilterActionState(filter));
}
return list;
}
private void showFilterDialog(RowObjectFilterModel<ROW_OBJECT> tableModel) {
if (columnFilterDialog == null) {
if (ColumnFilterDialog.hasFilterableColumns(table, tableModel)) {
columnFilterDialog = new ColumnFilterDialog<>(this, table, rowObjectFilterModel);
}
else {
Msg.showError(this, null, "Column Filter Error",
"This table contains no filterable columns!");
return;
}
}
DockingWindowManager.showDialog(table, columnFilterDialog);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private abstract class ColumnFilterActionState
extends ActionState<ColumnBasedTableFilter<ROW_OBJECT>> {
ColumnFilterActionState(String name, Icon icon, ColumnBasedTableFilter<ROW_OBJECT> filter) {
super(name, icon, filter);
}
abstract void performAction();
}
String getFilterName(ColumnBasedTableFilter<ROW_OBJECT> filter) {
String filterName = filter.getName();
return filterName == null ? "Unsaved" : filterName;
}
private class ClearFilterActionState extends ColumnFilterActionState {
public ClearFilterActionState() {
super("Clear Filter", CLEAR_FILTER_ICON, null);
}
@Override
void performAction() {
setFilter(null);
}
}
private class CreateFilterActionState extends ColumnFilterActionState {
public CreateFilterActionState() {
super("Create Column Filter", FILTER_OFF_ICON, null);
}
@Override
void performAction() {
showFilterDialog(rowObjectFilterModel);
}
}
private class EditFilterActionState extends ColumnFilterActionState {
public EditFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Edit: " + getFilterName(filter), FILTER_ON_ICON, filter);
}
@Override
void performAction() {
showFilterDialog(rowObjectFilterModel);
}
}
private class ApplyFilterActionState extends ColumnFilterActionState {
public ApplyFilterActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply: " + getFilterName(filter), APPLY_FILTER_ICON, filter);
}
@Override
void performAction() {
setFilter(getUserData());
}
}
private class ApplyLastUsedActionState extends ColumnFilterActionState {
public ApplyLastUsedActionState(ColumnBasedTableFilter<ROW_OBJECT> filter) {
super("Apply Last Unsaved", FILTER_ON_ICON, filter);
}
@Override
void performAction() {
setFilter(getUserData());
}
}
}

View File

@ -23,7 +23,6 @@ import javax.swing.JTable;
import org.jdom.Element;
import docking.DockingWindowManager;
import docking.widgets.table.GTableFilterPanel;
import docking.widgets.table.RowObjectTableModel;
import ghidra.framework.options.PreferenceState;
import ghidra.framework.options.SaveState;
@ -35,7 +34,7 @@ import ghidra.util.Msg;
* @param <R> the row type of the table.
*/
public class ColumnFilterSaveManager<R> {
private static final String COLUMN_FILTER_EXTENSION = ".ColumnFilterExtension";
private static final String COLUMN_FILTER_STATE = "COLUMN_FILTER_STATE";
private List<ColumnBasedTableFilter<R>> filters = new ArrayList<>();
@ -46,14 +45,15 @@ public class ColumnFilterSaveManager<R> {
/**
* Constructor
*
* @param panel The GTableFilterPanel for the table.
* @param tablePreferenceKey the key used to save table settings. This is used to make a
* preference key for saving the column filters.
* @param table The JTable that is filterable.
* @param model the TableModel that supports filtering.
* @param dataSource the table's DataSource object.
*/
public ColumnFilterSaveManager(GTableFilterPanel<R> panel, JTable table,
public ColumnFilterSaveManager(String tablePreferenceKey, JTable table,
RowObjectTableModel<R> model, Object dataSource) {
preferenceKey = panel.getPreferenceKey() + COLUMN_FILTER_EXTENSION;
preferenceKey = tablePreferenceKey + ColumnFilterManager.FILTER_EXTENSION;
loadFromPreferences(table, model, dataSource);
}

View File

@ -29,7 +29,6 @@ import docking.action.*;
import docking.widgets.OptionDialog;
import docking.widgets.dialogs.InputDialog;
import docking.widgets.label.GLabel;
import docking.widgets.table.GTableFilterPanel;
import docking.widgets.table.RowObjectFilterModel;
import docking.widgets.table.columnfilter.*;
import docking.widgets.table.constrainteditor.ColumnConstraintEditor;
@ -50,38 +49,38 @@ import utility.function.Callback;
public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
implements TableFilterDialogModelListener {
private final ColumnFilterDialogModel<R> filterModel;
private ColumnFilterManager<R> filterManager;
private ColumnFilterDialogModel<R> dialogModel;
private JTable table;
private RowObjectFilterModel<R> tableModel;
private JPanel filterPanelContainer;
private List<ColumnFilterPanel> filterPanels = new ArrayList<>();
private Callback closeCallback;
private GTableFilterPanel<R> gTableFilterPanel;
private JPanel bottomPanel;
private JTable table;
private RowObjectFilterModel<R> tableModel;
/**
* Constructor
*
* @param gTableFilterPanel the GTableFilterPanel that launched this dialog.
*
* @param filterManager the filter manager
* @param table the table being filtered.
* @param tableModel the table model.
*/
public ColumnFilterDialog(GTableFilterPanel<R> gTableFilterPanel, JTable table,
public ColumnFilterDialog(ColumnFilterManager<R> filterManager, JTable table,
RowObjectFilterModel<R> tableModel) {
super("Table Column Filters", WindowUtilities.areModalDialogsVisible(), true, true, false);
this.gTableFilterPanel = gTableFilterPanel;
this.filterManager = filterManager;
this.table = table;
this.tableModel = tableModel;
ColumnBasedTableFilter<R> columnTableFilter = gTableFilterPanel.getColumnTableFilter();
ColumnBasedTableFilter<R> columnTableFilter = filterManager.getCurrentFilter();
filterModel =
dialogModel =
new ColumnFilterDialogModel<>(tableModel, table.getColumnModel(), columnTableFilter);
filterModel.addListener(this);
dialogModel.addListener(this);
setHelpLocation(new HelpLocation("Trees", "Column_Filters"));
addWorkPanel(buildMainPanel());
@ -97,10 +96,9 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
updateStatus();
}
public static <R> boolean hasFilterableColumns(JTable table,
RowObjectFilterModel<R> model) {
public static <R> boolean hasFilterableColumns(JTable table, RowObjectFilterModel<R> model) {
return !ColumnFilterDialogModel.getAllColumnFilterData(model, table.getColumnModel())
.isEmpty();
.isEmpty();
}
private void addClearFilterButton() {
@ -119,7 +117,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
DockingAction saveAction = new DockingAction("Save", "Filter") {
@Override
public boolean isEnabledForContext(ActionContext context) {
return !filterModel.getFilterRows().isEmpty() && filterModel.isValid();
return !dialogModel.getFilterRows().isEmpty() && dialogModel.isValid();
}
@Override
@ -140,15 +138,15 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
};
loadAction.setDescription("Load Filter");
loadAction.setHelpLocation(new HelpLocation("Trees", "Load_Filter"));
loadAction.setToolBarData(
new ToolBarData(Icons.OPEN_FOLDER_ICON));
loadAction.setToolBarData(new ToolBarData(Icons.OPEN_FOLDER_ICON));
addAction(loadAction);
}
private void saveFilter() {
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(
gTableFilterPanel, table, tableModel, filterModel.getDataSource());
ColumnBasedTableFilter<R> filter = filterModel.getTableColumnFilter();
String preferenceKey = filterManager.getPreferenceKey();
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(preferenceKey,
table, tableModel, dialogModel.getDataSource());
ColumnBasedTableFilter<R> filter = dialogModel.getTableColumnFilter();
String defaultName = new Date().toString();
InputDialog dialog = new InputDialog("Save Filter", "Filter Name: ", defaultName, d -> {
@ -174,13 +172,14 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
filter.setName(filterName);
filterSaveManager.addFilter(filter);
filterSaveManager.save();
gTableFilterPanel.updateSavedFilters(filter, true);
filterModel.setFilter(filter);
filterManager.updateSavedFilters(filter, true);
dialogModel.setFilter(filter);
}
private void loadFilter() {
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(
gTableFilterPanel, table, tableModel, filterModel.getDataSource());
String preferenceKey = filterManager.getPreferenceKey();
ColumnFilterSaveManager<R> filterSaveManager = new ColumnFilterSaveManager<>(preferenceKey,
table, tableModel, dialogModel.getDataSource());
List<ColumnBasedTableFilter<R>> savedFilters = filterSaveManager.getSavedFilters();
if (savedFilters.isEmpty()) {
Msg.showInfo(this, getComponent(), "No Saved Filters",
@ -195,7 +194,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
ColumnBasedTableFilter<R> selectedFilter = archiveDialog.getSelectedColumnFilter();
if (selectedFilter != null) {
filterModel.setFilter(selectedFilter);
dialogModel.setFilter(selectedFilter);
}
}
@ -223,14 +222,12 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
bottomPanel = new JPanel(new BorderLayout());
JPanel innerPanel = new JPanel(new VerticalLayout(3));
JButton addAndConditionButton =
new JButton("Add AND condition", Icons.ADD_ICON);
JButton addAndConditionButton = new JButton("Add AND condition", Icons.ADD_ICON);
addAndConditionButton.addActionListener(e -> addFilterCondition(LogicOperation.AND));
addAndConditionButton.setEnabled(true);
JButton addOrConditionButton =
new JButton("Add OR condition", Icons.ADD_ICON);
JButton addOrConditionButton = new JButton("Add OR condition", Icons.ADD_ICON);
addOrConditionButton.setHorizontalAlignment(SwingConstants.LEFT);
addOrConditionButton.addActionListener(e -> addFilterCondition(LogicOperation.OR));
@ -251,7 +248,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
sb.append("Column Filter");
ColumnBasedTableFilter<R> filter = filterModel.getTableColumnFilter();
ColumnBasedTableFilter<R> filter = dialogModel.getTableColumnFilter();
if (filter != null && filter.getName() != null) {
sb.append(": ").append(filter.getName());
}
@ -275,7 +272,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
// * Dialog state is different from applied filter and valid - prompt to apply filter.
// * Dialog state is different from applied filter, but invalid - prompt if should really close
if (!filterModel.hasUnappliedChanges()) {
if (!dialogModel.hasUnappliedChanges()) {
return true;
}
if (dialogHasValidFilter()) {
@ -316,12 +313,12 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
private boolean dialogHasValidFilter() {
return filterModel.getTableColumnFilter() != null;
return dialogModel.getTableColumnFilter() != null;
}
@Override
protected void dialogClosed() {
filterModel.dispose();
dialogModel.dispose();
if (closeCallback != null) {
closeCallback.call();
}
@ -339,28 +336,29 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
private void clearFilter() {
this.gTableFilterPanel.setColumnTableFilter(null);
filterModel.clear();
filterManager.setFilter(null);
dialogModel.clear();
updateStatus();
}
private void applyFilter() {
ColumnBasedTableFilter<R> tableColumnFilter = filterModel.getTableColumnFilter();
filterModel.setCurrentlyAppliedFilter(tableColumnFilter);
this.gTableFilterPanel.setColumnTableFilter(tableColumnFilter);
ColumnBasedTableFilter<R> tableColumnFilter = dialogModel.getTableColumnFilter();
dialogModel.setCurrentlyAppliedFilter(tableColumnFilter);
filterManager.setFilter(tableColumnFilter);
}
private void loadFilterRows() {
filterPanelContainer.removeAll();
filterPanels.clear();
List<DialogFilterRow> filterRows = filterModel.getFilterRows();
List<DialogFilterRow> filterRows = dialogModel.getFilterRows();
for (int i = 0; i < filterRows.size(); i++) {
DialogFilterRow filterRow = filterRows.get(i);
ColumnFilterPanel panel = new ColumnFilterPanel(filterRow);
if (i != 0) {
filterPanelContainer.add(
createLogicalOperationLabel(filterRow.getLogicOperation()));
LogicOperation op = filterRow.getLogicOperation();
GLabel label = createLogicalOperationLabel(op);
filterPanelContainer.add(label);
}
filterPanelContainer.add(panel);
filterPanels.add(panel);
@ -382,14 +380,14 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
headerPanel.add(new GLabel("Filter", SwingConstants.CENTER));
headerPanel.add(new GLabel("Filter Value", SwingConstants.CENTER));
headerPanel.setBorder(new CompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 0, Colors.BORDER),
BorderFactory.createEmptyBorder(4, 0, 4, 0)));
headerPanel.setBorder(
new CompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Colors.BORDER),
BorderFactory.createEmptyBorder(4, 0, 4, 0)));
return headerPanel;
}
private void addFilterCondition(LogicOperation logicalOperation) {
filterModel.createFilterRow(logicalOperation);
dialogModel.createFilterRow(logicalOperation);
scrollFilterPanelToBottom();
}
@ -410,7 +408,7 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
void updateStatus() {
setStatusText(getStatusMessage());
boolean isValid = filterModel.isValid();
boolean isValid = dialogModel.isValid();
setOkEnabled(isValid);
setApplyEnabled(isValid);
@ -423,11 +421,11 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
public void filterChanged(ColumnBasedTableFilter<R> newFilter) {
if (Objects.equals(newFilter, filterModel.getTableColumnFilter())) {
if (Objects.equals(newFilter, dialogModel.getTableColumnFilter())) {
return;
}
getComponent().requestFocus(); // work around for java parenting bug where dialog appears behind
if (filterModel.hasUnappliedChanges()) {
if (dialogModel.hasUnappliedChanges()) {
int result = OptionDialog.showYesNoDialog(getComponent(), "Filter Changed",
"The filter has been changed externally.\n" +
" Do you want to update this editor and lose your current changes?");
@ -435,14 +433,14 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
return;
}
}
filterModel.setFilter(newFilter);
dialogModel.setFilter(newFilter);
}
private String getStatusMessage() {
if (filterModel.isEmpty()) {
if (dialogModel.isEmpty()) {
return "Please add a filter condition!";
}
if (!filterModel.isValid()) {
if (!dialogModel.isValid()) {
return "One or more filter values are invalid!";
}
return "";
@ -473,8 +471,6 @@ public class ColumnFilterDialog<R> extends ReusableDialogComponentProvider
}
void filterRemoved(ColumnBasedTableFilter<R> filter) {
gTableFilterPanel.updateSavedFilters(filter, false);
filterManager.updateSavedFilters(filter, false);
}
}

View File

@ -193,6 +193,12 @@ public class ConcurrentQBuilder<I, R> {
return this;
}
/**
* Builds the final {@link ConcurrentQ}.
*
* @param callback the callback for processing each job
* @return the new queue
*/
public ConcurrentQ<I, R> build(QCallback<I, R> callback) {
ConcurrentQ<I, R> concurrentQ = new ConcurrentQ<>(callback, getQueue(), getThreadPool(),

View File

@ -17,7 +17,7 @@ package ghidra.util;
/**
* Ghidra synchronization lock. This class allows creation of named locks for
* synchroniing modification of multiple tables in the Ghidra database.
* synchronizing modification of multiple tables in the Ghidra database.
*/
public class Lock {
private Thread owner;