diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/function_tag_window.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/function_tag_window.htm index d5ee8c7e62..a8a6d01654 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/function_tag_window.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/function_tag_window.htm @@ -34,25 +34,20 @@
-
- All Tags TAble: Displays all tags that are available to assign to the +
- All Tags: Displays all tags that are available to assign to the current function, as well as a count of the number of times each tag has been used in the current program.
-- Assigned Tags List: Displays all tags that have been assigned to the current +
- Assigned Tags: Displays all tags that have been assigned to the current function.
-- Function Panel: Displays functions that contain any selected tags. If multiple +
- Functions: Displays functions that contain any selected tags. If multiple tags are selected, functions containing ANY of the tags will be displayed.
- Tag Input Field: Allows users to create new tags. Multiple tags may be created at one time.
- -
- - Filter Field: Allows users to filter what is shown in the Available and Assigned tag lists. -
-
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png index 35ae640794..f869f9ae6f 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/AllFunctionsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/AllFunctionsPanel.java index da157492eb..6144f912c4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/AllFunctionsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/AllFunctionsPanel.java @@ -16,23 +16,16 @@ package ghidra.app.plugin.core.function.tags; import java.awt.BorderLayout; -import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import javax.swing.*; -import org.apache.commons.lang3.StringUtils; - -import docking.widgets.table.GTableFilterPanel; -import docking.widgets.table.columnfilter.ColumnBasedTableFilter; -import docking.widgets.table.columnfilter.LogicOperation; -import docking.widgets.table.constraint.*; import ghidra.app.services.GoToService; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.program.model.listing.*; -import ghidra.util.table.GhidraTable; -import ghidra.util.table.field.AddressBasedLocation; +import ghidra.util.table.*; /** * Displays all functions that are associated with the selected tag in the @@ -41,10 +34,9 @@ import ghidra.util.table.field.AddressBasedLocation; public class AllFunctionsPanel extends JPanel { private FunctionTableModel model; - private FunctionTableModel filteredModel; - private JLabel titleLabel; - private String filterText; private GhidraTable table; + private GhidraTableFilterPanel
filterPanel; + private JLabel titleLabel; /** * Constructor @@ -56,16 +48,19 @@ public class AllFunctionsPanel extends JPanel { public AllFunctionsPanel(Program program, ComponentProviderAdapter provider, String title) { model = new FunctionTableModel(title, provider.getTool(), program, null); - filteredModel = new FunctionTableModel(title, provider.getTool(), program, null); + GhidraThreadedTablePanel tablePanel = + new GhidraThreadedTablePanel<>(model); - table = new GhidraTable(filteredModel); + table = tablePanel.getTable(); + filterPanel = new GhidraTableFilterPanel<>(table, model); setLayout(new BorderLayout()); titleLabel = new JLabel(title); titleLabel.setBorder(BorderFactory.createEmptyBorder(3, 5, 0, 0)); add(titleLabel, BorderLayout.NORTH); - add(new JScrollPane(table), BorderLayout.CENTER); + add(tablePanel, BorderLayout.CENTER); + add(filterPanel, BorderLayout.SOUTH); GoToService goToService = provider.getTool().getService(GoToService.class); if (goToService != null) { @@ -73,26 +68,11 @@ public class AllFunctionsPanel extends JPanel { } } - /** - * Filters the table based on the given text - * - * Note that this panel shares a filter with the other panels in the - * function tag dialog, so it doesn't use a dedicated - * {@link GTableFilterPanel}. - * - * @param filterString the filter text - */ - public void setFilterText(String filter) { - this.filterText = filter; - applyFilter(); - } - /** * Updates the table with whatever is in the {@link #model} */ public void refresh() { - applyFilter(); - filteredModel.refresh(); + model.reload(); } /** @@ -100,7 +80,7 @@ public class AllFunctionsPanel extends JPanel { * * @param selectedTags the selected function tags */ - public void refresh(List
selectedTags) { + public void refresh(Set selectedTags) { setSelectedTags(selectedTags); } @@ -111,7 +91,6 @@ public class AllFunctionsPanel extends JPanel { */ public void setProgram(Program program) { model.setProgram(program); - filteredModel.setProgram(program); } /** @@ -120,7 +99,7 @@ public class AllFunctionsPanel extends JPanel { * * @param tags the selected tags */ - public void setSelectedTags(List tags) { + public void setSelectedTags(Set tags) { String tagNames = tags.stream() .map(t -> t.getName()) .collect(Collectors.joining(" or ")) @@ -128,8 +107,6 @@ public class AllFunctionsPanel extends JPanel { titleLabel.setText("Functions With Tag: " + tagNames); model.setSelectedTags(tags); - filteredModel.setSelectedTags(tags); - applyFilter(); } /** @@ -151,51 +128,4 @@ public class AllFunctionsPanel extends JPanel { public FunctionTableModel getTableModel() { return model; } - - private void applyFilter() { - if (StringUtils.isEmpty(filterText)) { - return; - } - - ColumnBasedTableFilter tableFilter = new ColumnBasedTableFilter<>(filteredModel); - tableFilter.addConstraintSet(LogicOperation.OR, 0, - Arrays.asList(new StringContainsColumnConstraint(filterText))); - tableFilter.addConstraintSet(LogicOperation.OR, 1, Arrays.asList( - new LocationContainsColumnConstraint(new StringContainsColumnConstraint(filterText)))); - tableFilter.addConstraintSet(LogicOperation.OR, 2, - Arrays.asList(new StringContainsColumnConstraint(filterText))); - - filteredModel.setTableFilter(tableFilter); - filteredModel.reFilter(); - } - - /** - * Converts an {@link AddressBasedLocation} to a string - */ - private class LocationToStringMapper - extends ColumnTypeMapper { - - @Override - public String convert(AddressBasedLocation value) { - return value.toString(); - } - - } - - /** - * Column constraint stipulating that a given {@link AddressBasedLocation} - * contains a specified string - */ - private class LocationContainsColumnConstraint - extends MappedColumnConstraint { - - public LocationContainsColumnConstraint(ColumnConstraint delegate) { - super(new LocationToStringMapper(), delegate); - } - - @Override - public ColumnConstraint copy(ColumnConstraint value) { - return new LocationContainsColumnConstraint(value); - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTableModel.java index cb4b62f016..b008b5e65e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTableModel.java @@ -16,6 +16,7 @@ package ghidra.app.plugin.core.function.tags; import java.util.List; +import java.util.Set; import docking.widgets.table.DiscoverableTableUtils; import docking.widgets.table.TableColumnDescriptor; @@ -37,7 +38,7 @@ import ghidra.util.task.TaskMonitor; class FunctionTableModel extends AddressBasedTableModel { // The function tags to display functions for - private List tags; + private Set tags; /** * Constructor @@ -104,7 +105,7 @@ class FunctionTableModel extends AddressBasedTableModel { * * @param tags the selected tags */ - public void setSelectedTags(List tags) { + public void setSelectedTags(Set tags) { this.tags = tags; reload(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java index ca117c013b..6cc4bc68a4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagLoader.java @@ -16,8 +16,8 @@ package ghidra.app.plugin.core.function.tags; import java.io.*; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import org.xml.sax.*; @@ -29,17 +29,20 @@ import ghidra.xml.*; /** * Reads function tags from @see ghidra.framework.Application#getModuleDataFile(java.lang.String) - * or a File on the filesytem. + * or a File on the filesystem. */ public class FunctionTagLoader { + // TODO this class should provide a system property to allow users to load files defined in + // the property + /** * Load function tags from filesystem. Useful for unit tests. * * @param tagFile tag file * @return List list of function tags */ - protected static List loadTags(File tagFile) { + protected static Set loadTags(File tagFile) { return loadTags(new ResourceFile(tagFile)); } @@ -49,18 +52,19 @@ public class FunctionTagLoader { * @param moduleDataFilePath data file loaded by Application * @return List list of function tags */ - protected static List loadTags(String moduleDataFilePath) { + protected static Set loadTags(String moduleDataFilePath) { try { return loadTags(Application.getModuleDataFile(moduleDataFilePath)); } catch (FileNotFoundException e) { - Msg.error(null, "Error loading function tags file from " + moduleDataFilePath, e); + Msg.error(FunctionTagLoader.class, + "Error loading function tags file from " + moduleDataFilePath, e); } - return new ArrayList (); + return new HashSet<>(); } - protected static List loadTags(final ResourceFile tagDataFile) { - List tags = new ArrayList<>(); + protected static Set loadTags(final ResourceFile tagDataFile) { + Set tags = new HashSet<>(); try { ErrorHandler errHandler = new ErrorHandler() { @@ -101,7 +105,7 @@ public class FunctionTagLoader { el = parser.next(); comment = parser.end().getText(); } - FunctionTagTemp tag = new FunctionTagTemp(name, comment); + InMemoryFunctionTag tag = new InMemoryFunctionTag(name, comment); tags.add(tag); } } @@ -109,10 +113,12 @@ public class FunctionTagLoader { parser.dispose(); } catch (XmlException e) { - Msg.error(null, "Error parsing function tags from " + tagDataFile, e); + Msg.error(FunctionTagLoader.class, "Error parsing function tags from " + tagDataFile, + e); } catch (SAXException | IOException e) { - Msg.error(null, "Error loading function tags from " + tagDataFile, e); + Msg.error(FunctionTagLoader.class, "Error loading function tags from " + tagDataFile, + e); } return tags; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagRowObject.java new file mode 100644 index 0000000000..14587fd5ba --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagRowObject.java @@ -0,0 +1,52 @@ +/* ### + * 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.app.plugin.core.function.tags; + +import ghidra.program.model.listing.FunctionTag; + +class FunctionTagRowObject { + + private FunctionTag tag; + private int count; + + FunctionTagRowObject(FunctionTag tag) { + this.tag = tag; + } + + FunctionTag getTag() { + return tag; + } + + String getName() { + return tag.getName(); + } + + int getCount() { + return count; + } + + void setCount(int count) { + this.count = count; + } + + boolean isImmutable() { + return tag instanceof InMemoryFunctionTag; + } + + String getComment() { + return tag.getComment(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java index 55b1f09bdb..74d5a76178 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTable.java @@ -21,29 +21,32 @@ import java.awt.event.MouseEvent; import java.util.Set; import javax.swing.JTable; -import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; +import docking.widgets.table.GTableCellRenderingData; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionTag; import ghidra.util.HTMLUtilities; import ghidra.util.table.GhidraTable; +import ghidra.util.table.GhidraTableCellRenderer; /** * Table that displays function tags and a count of the number of times * each tag has been used */ public class FunctionTagTable extends GhidraTable { - + /** * If true, disable any rows that have already been assigned * to a function (and thus cannot be added again) */ private boolean disable = false; - + /** The selected function */ private Function function = null; - + + private TagRenderer renderer = new TagRenderer(); + /** * Constructor * @@ -52,31 +55,29 @@ public class FunctionTagTable extends GhidraTable { public FunctionTagTable(FunctionTagTableModel model) { super(model); } - + protected void setDisabled(boolean disable) { this.disable = disable; } - + public void setFunction(Function function) { this.function = function; } - + @Override public String getToolTipText(MouseEvent evt) { FunctionTagTable table = (FunctionTagTable) evt.getSource(); int row = this.rowAtPoint(evt.getPoint()); - int nameCol = table.getColumnModel().getColumnIndex("Name"); - String tagName = (String)table.getValueAt(row, nameCol); FunctionTagTableModel model = (FunctionTagTableModel) table.getModel(); - FunctionTag tag = model.getTag(tagName); - - if (tag.getComment().isEmpty()) { + FunctionTagRowObject rowObject = model.getRowObject(row); + String comment = rowObject.getComment(); + if (comment.isEmpty()) { return "no tooltip set"; } - return "" + HTMLUtilities.escapeHTML(tag.getComment()); + return "" + HTMLUtilities.escapeHTML(comment); } - + /** * We need to override the renderer for the following cases: * - italicize tags that cannot be edited
@@ -84,42 +85,40 @@ public class FunctionTagTable extends GhidraTable { */ @Override public TableCellRenderer getCellRenderer(int row, int col) { - - return new TableCellRenderer() { - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) { - - DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); - Component c = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - - boolean enableRow = true; - if (disable && function != null) { - int nameCol = table.getColumnModel().getColumnIndex("Name"); - String nameVal = (String)table.getValueAt(row, nameCol); - Settags = function.getTags(); - enableRow = !tags.stream().anyMatch(t -> t.getName().equals(nameVal)); - } - c.setEnabled(enableRow); - - switch (table.getColumnName(column)) { - case "Count": - break; - case "Name": - FunctionTagTableModel model = (FunctionTagTableModel) table.getModel(); - FunctionTag tag = model.getTag((String)value); - if (tag instanceof FunctionTagTemp) { - c.setFont(getFont().deriveFont(Font.ITALIC)); - } - else { - c.setFont(getFont().deriveFont(Font.PLAIN)); - } - break; - } - - return c; + return renderer; + } + + private class TagRenderer extends GhidraTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + Component c = super.getTableCellRendererComponent(data); + + JTable table = data.getTable(); + int nameColumn = table.getColumnModel().getColumnIndex("Name"); + + int row = data.getRowViewIndex(); + FunctionTagTableModel model = (FunctionTagTableModel) table.getModel(); + FunctionTagRowObject rowObject = model.getRowObject(row); + + boolean enableRow = true; + if (disable && function != null) { + String tagName = rowObject.getName(); + Set tags = function.getTags(); + enableRow = !tags.stream().anyMatch(t -> t.getName().equals(tagName)); } - }; + c.setEnabled(enableRow); + + c.setFont(getFont().deriveFont(Font.PLAIN)); + + int column = data.getColumnViewIndex(); + if (column == nameColumn) { + if (rowObject.isImmutable()) { + c.setFont(getFont().deriveFont(Font.ITALIC)); + } + } + + return c; + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java index 60b284eddd..0e3ff066de 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java @@ -15,192 +15,158 @@ */ package ghidra.app.plugin.core.function.tags; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.function.Supplier; + +import org.apache.commons.collections4.map.LazyMap; import docking.widgets.table.AbstractDynamicTableColumnStub; import docking.widgets.table.TableColumnDescriptor; import docking.widgets.table.threaded.ThreadedTableModel; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.FunctionIterator; -import ghidra.program.model.listing.FunctionTag; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.*; import ghidra.util.datastruct.Accumulator; +import ghidra.util.datastruct.Counter; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; /** * Model that backs a {@link FunctionTagTable} */ -public class FunctionTagTableModel extends ThreadedTableModel > { +public class FunctionTagTableModel extends ThreadedTableModel { - /** The list of tags to display in the table */ - private List tags = new ArrayList<>(); private Program program; - - /** - * Constructor - * - * @param modelName the name of this table model - * @param serviceProvider the service provider - */ - protected FunctionTagTableModel(String modelName, ServiceProvider serviceProvider) { + private Supplier > tagLoader; + + protected FunctionTagTableModel(String modelName, ServiceProvider serviceProvider, + Supplier > tagLoader) { super(modelName, serviceProvider); + this.tagLoader = tagLoader; } - + public void setProgram(Program program) { this.program = program; } @Override - protected void doLoad(Accumulator accumulator, TaskMonitor monitor) + protected void doLoad(Accumulator accumulator, TaskMonitor monitor) throws CancelledException { - accumulator.addAll(tags); - fireTableDataChanged(); + + if (program == null) { + return; + } + + Map countsByName = getFunctionTagCountsByName(monitor); + for (FunctionTag tag : tagLoader.get()) { + + FunctionTagRowObject rowObject = new FunctionTagRowObject(tag); + Counter counter = countsByName.get(tag.getName()); + rowObject.setCount(counter.count); + accumulator.add(rowObject); + } + } + + private Map getFunctionTagCountsByName(TaskMonitor monitor) + throws CancelledException { + + FunctionManager functionManager = program.getFunctionManager(); + monitor.initialize(functionManager.getFunctionCount()); + + Map map = LazyMap.lazyMap(new HashMap<>(), () -> new Counter()); + FunctionIterator it = functionManager.getFunctions(true); + for (Function function : it) { + monitor.checkCanceled(); + + Set functionTags = function.getTags(); + for (FunctionTag tag : functionTags) { + + String name = tag.getName(); + map.get(name).count++; + } + + monitor.incrementProgress(1); + } + return map; } @Override - protected TableColumnDescriptor createTableColumnDescriptor() { - TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); - - descriptor.addVisibleColumn(new FunctionTagNameColumn()); + protected TableColumnDescriptor createTableColumnDescriptor() { + TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); + + descriptor.addVisibleColumn(new FunctionTagNameColumn()); descriptor.addVisibleColumn(new FunctionTagCountColumn()); return descriptor; } - + @Override - public List getDataSource() { - return tags; + public Program getDataSource() { + return program; } - - /** - * Adds a function tag to the table. If a tag with the same name is already - * present in the table, does nothing. - * - * @param tag the function tag to add - */ - public void addTag(FunctionTag tag) { - Optional existingTag = tags.stream() - .filter(t -> t.getName().equals(tag.getName())) - .findAny(); - if (existingTag.isPresent()) { - tags.remove(existingTag.get()); - } - - tags.add(tag); - fireTableDataChanged(); - } - + /** * Removes all function tags from the model */ public void clear() { - tags.clear(); - fireTableDataChanged(); + super.clearData(); } - - /** - * Returns all function tags in the model - * - * @return all function tags - */ - public List getTags() { - return tags; - } - - /** - * Returns the {@link FunctionTag} object with a given name - * - * @param name the tag name to search for - * @return the function tag - */ - public FunctionTag getTag(String name) { - Optional tag = tags.stream() - .filter(t -> t.getName().equals(name)) - .findAny(); - if (tag.isPresent()) { - return tag.get(); - } - - return null; - } - + /** * Returns true if a function tag with a given name is in the model * - * @param name the tag name to search fo + * @param name the tag name to search for * @return true if the tag exists in the model */ - public boolean isTagInModel(String name) { - Optional tag = tags.stream() - .filter(t -> t.getName().equals(name)) - .findAny(); - return tag.isPresent(); + public boolean containsTag(String name) { + return getModelData().stream().anyMatch(row -> row.getName().equals(name)); } - + /** * Table column that displays a count of the number of times a function tag has been * applied to a function (in the selected program) */ - private class FunctionTagCountColumn extends AbstractDynamicTableColumnStub { + private class FunctionTagCountColumn + extends AbstractDynamicTableColumnStub { @Override public String getColumnDisplayName(Settings settings) { - return " "; // don't display any name, but need it to be at least one space - // wide so the correct space is allocated to the header + // don't display any name, but need it to be at least one space wide so the correct + // space is allocated to the header + return " "; } - + @Override public String getColumnName() { return "Count"; } - + @Override public int getColumnPreferredWidth() { return 30; } - + @Override - public Integer getValue(FunctionTag rowObject, Settings settings, - ServiceProvider serviceProvider) throws IllegalArgumentException { - int count = 0; - - if (program == null) { - return 0; - } - - FunctionIterator iter = program.getFunctionManager().getFunctions(true); - while (iter.hasNext()) { - Function f = iter.next(); - Optional foundTag = f.getTags() - .stream() - .filter(t -> t.getName().equals(rowObject.getName())) - .findAny(); - if (foundTag.isPresent()) { - count++; - } - } - return count; + public Integer getValue(FunctionTagRowObject rowObject, Settings settings, + ServiceProvider sp) throws IllegalArgumentException { + return rowObject.getCount(); } } - + /** * Table column that displays the name of a function tag */ - private class FunctionTagNameColumn extends AbstractDynamicTableColumnStub { - + private class FunctionTagNameColumn + extends AbstractDynamicTableColumnStub { + @Override public String getColumnName() { return "Name"; } - + @Override - public String getValue(FunctionTag rowObject, Settings settings, - ServiceProvider serviceProvider) throws IllegalArgumentException { + public String getValue(FunctionTagRowObject rowObject, Settings settings, + ServiceProvider sp) throws IllegalArgumentException { return rowObject.getName(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagsComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagsComponentProvider.java index 2bfc102680..0522aaaf4b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagsComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagsComponentProvider.java @@ -16,10 +16,9 @@ package ghidra.app.plugin.core.function.tags; import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; +import java.util.Set; import javax.swing.*; @@ -65,9 +64,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter private JPanel mainPanel; private JPanel inputPanel; - private JPanel filterPanel; - private HintTextField tagInputTF; - private HintTextField filterInputTF; + private HintTextField tagInputField; private int MIN_WIDTH = 850; private int MIN_HEIGHT = 350; @@ -104,12 +101,8 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter */ public void reload() { - SystemUtilities.runSwingLater(() -> { - - if (tagInputTF != null) { - tagInputTF.setText(""); - } - + Swing.runLater(() -> { + tagInputField.setText(""); updateTitle(currentLocation); updateTagLists(); }); @@ -127,6 +120,10 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter return mainPanel; } + HintTextField getTagInputField() { + return tagInputField; + } + /** * Invoked when a new location has been detected in the listing. When * this happens we need to update the tag list to show what tags are assigned @@ -197,7 +194,6 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS)); bottomPanel.add(createInputPanel()); - bottomPanel.add(createFilterPanel()); mainPanel.add(bottomPanel, BorderLayout.SOUTH); mainPanel.setPreferredSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); @@ -254,12 +250,20 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter sourcePanel.clearSelection(); } - List sourceTags = sourcePanel.getSelectedTags(); - List targetTags = targetPanel.getSelectedTags(); + Set sourceTags = sourcePanel.getSelectedTags(); + Set targetTags = targetPanel.getSelectedTags(); sourceTags.addAll(targetTags); allFunctionsPanel.setSelectedTags(sourceTags); } + TargetTagsPanel getTargetPanel() { + return targetPanel; + } + + SourceTagsPanel getSourcePanel() { + return sourcePanel; + } + /** * Returns the {@link Function} at the given program location. If not a function, or * if the location is not a pointer to a function returns null. @@ -319,11 +323,10 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter targetPanel.setProgram(program); allFunctionsPanel.setProgram(program); - // Get the currently selected tags and use them to update the - // all functions panel. If there is no current selection, leave the - // table as-is. - List sTags = sourcePanel.getSelectedTags(); - List tTags = targetPanel.getSelectedTags(); + // Get the currently selected tags and use them to update the all functions panel. If + // there is no current selection, leave the table as-is. + Set sTags = sourcePanel.getSelectedTags(); + Set tTags = targetPanel.getSelectedTags(); sTags.addAll(tTags); if (!sTags.isEmpty()) { allFunctionsPanel.refresh(sTags); @@ -369,7 +372,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter private List getInputNames() { // First split the string on the delimiter to get all the entries. - String[] names = tagInputTF.getText().split(INPUT_DELIMITER); + String[] names = tagInputField.getText().split(INPUT_DELIMITER); // Trim each item to remove any leading/trailing whitespace and add to // the return list. @@ -383,41 +386,6 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter return nameList; } - /** - * Creates a panel that allows users to enter text that will be used - * as a filter on the source and target lists. - * - * @return the new filter panel - */ - private JPanel createFilterPanel() { - filterPanel = new JPanel(new BorderLayout()); - - filterInputTF = new HintTextField(""); - filterInputTF.setName("filterInputTF"); - filterInputTF.addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - JTextField textField = (JTextField) e.getSource(); - String text = textField.getText(); - sourcePanel.setFilterText(text); - targetPanel.setFilterText(text); - allFunctionsPanel.setFilterText(text); - - if (!text.isEmpty()) { - filterInputTF.setBackground(Color.YELLOW); - } - else { - filterInputTF.setBackground(Color.WHITE); - } - } - }); - - filterPanel.add(new GLabel(" Tag Filter:"), BorderLayout.WEST); - filterPanel.add(filterInputTF, BorderLayout.CENTER); - - return filterPanel; - } - /** * Creates the text-entry panel for adding new tag names. * @@ -426,12 +394,12 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter private JPanel createInputPanel() { inputPanel = new JPanel(new BorderLayout()); - tagInputTF = new HintTextField("tag 1, tag 2, ..."); - tagInputTF.setName("tagInputTF"); - tagInputTF.addActionListener(e -> processCreates()); + tagInputField = new HintTextField("tag 1, tag 2, ..."); + tagInputField.setName("tagInputTF"); + tagInputField.addActionListener(e -> processCreates()); inputPanel.add(new GLabel(" Create new tag(s):"), BorderLayout.WEST); - inputPanel.add(tagInputTF, BorderLayout.CENTER); + inputPanel.add(tagInputField, BorderLayout.CENTER); return inputPanel; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTemp.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/InMemoryFunctionTag.java similarity index 81% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTemp.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/InMemoryFunctionTag.java index 3e9446d0e4..7f8b7c6c27 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTemp.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/InMemoryFunctionTag.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.function.tags; +import java.util.Objects; + import ghidra.program.model.listing.FunctionTag; /** @@ -22,23 +24,18 @@ import ghidra.program.model.listing.FunctionTag; * tags that are not yet ready to be inserted into the database. This was created * to allow tags to be imported from an external file and made available to the user * through the {@link FunctionTagsComponentProvider} UI without needing to formally - * add them to the {@link FunctionTagAdapter} table. + * add them to the {@code FunctionTagAdapter} table. */ -class FunctionTagTemp implements FunctionTag { +class InMemoryFunctionTag implements FunctionTag { private final String name; private final String comment; - FunctionTagTemp(String name, String comment) { + InMemoryFunctionTag(String name, String comment) { this.name = name; - this.comment = comment; + this.comment = comment == null ? "" : comment; } - /** - * These objects are not in the database, so just return a bogus id. - * - * @return - */ @Override public long getId() { return -1; @@ -93,23 +90,16 @@ class FunctionTagTemp implements FunctionTag { if (!(obj instanceof FunctionTag)) { return false; } + FunctionTag other = (FunctionTag) obj; - if (comment == null) { - if (other.getComment() != null) { - return false; - } - } - else if (!comment.equals(other.getComment())) { + if (!Objects.equals(comment, other.getComment())) { return false; } - if (name == null) { - if (other.getName() != null) { - return false; - } - } - else if (!name.equals(other.getName())) { + + if (!Objects.equals(name, other.getName())) { return false; } + return true; } @@ -118,4 +108,8 @@ class FunctionTagTemp implements FunctionTag { // These items cannot be deleted, so do nothing } + @Override + public String toString() { + return "In-memory tag: " + name; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java index 35b196825e..204acf6140 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java @@ -15,12 +15,7 @@ */ package ghidra.app.plugin.core.function.tags; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; +import java.util.*; import ghidra.app.cmd.function.AddFunctionTagCmd; import ghidra.app.cmd.function.CreateFunctionTagCmd; @@ -29,7 +24,6 @@ import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionTag; -import ghidra.util.Msg; import resources.ResourceManager; /** @@ -49,20 +43,15 @@ public class SourceTagsPanel extends TagListPanel { * TAG1 *tag comment * - * + * * */ private static String TAG_FILE = "functionTags.xml"; - // List of tags read in from the external file. These will be displayed in a different - // color than 'regular' tags and cannot be edited or deleted, until they're added to a function - private ListtempTags = new ArrayList<>(); - - // Keeps a list of the original temp tags as loaded from file. This is necessary - // when switching between programs where we need to know the original state of the - // temporary tags. Without this we would need to reload from file on each new program - // activation. - private List tempTagsCache; + // Keeps a list of the original tags as loaded from file. This is necessary when switching + // between programs where we need to know the original state of the disabled tags. Without + // this we would need to reload from file on each new program activation. + private Set tagsFromFile; /** * Constructor @@ -74,32 +63,30 @@ public class SourceTagsPanel extends TagListPanel { public SourceTagsPanel(FunctionTagsComponentProvider provider, PluginTool tool, String title) { super(provider, tool, title); - // Load any tags from external sources and keep a copy in the cache - tempTags = loadTags(); - tempTagsCache = new ArrayList<>(tempTags); - + // Load any tags from external sources + tagsFromFile = loadTags(); + table.setDisabled(true); } /****************************************************************************** * PUBLIC METHODS ******************************************************************************/ - + /** - * Adds any selected tags to the function currently selected in the - * listing. + * Adds any selected tags to the function currently selected in the listing */ public void addSelectedTags() { if (function == null) { return; } - List selectedTags = getSelectedTags(); + Set selectedTags = getSelectedTags(); for (FunctionTag tag : selectedTags) { // If the tag is one that has not yet been created (a temp tag), first create it, // then add it to the function. - if (tag instanceof FunctionTagTemp) { + if (tag instanceof InMemoryFunctionTag) { Command cmd = new CreateFunctionTagCmd(tag.getName(), tag.getComment()); tool.execute(cmd, program); } @@ -110,64 +97,39 @@ public class SourceTagsPanel extends TagListPanel { } @Override - public void refresh(Function function) { - + public void refresh(Function newFunction) { model.clear(); - - this.function = function; + this.function = newFunction; + table.setFunction(function); + model.reload(); + } + + @Override + protected Set backgroundLoadTags() { + + List extends FunctionTag> dbTags = getAllTagsFromDatabase(); + + // Add any tags from the file system that are not in the db + Set allTags = new HashSet<>(dbTags); + allTags.addAll(tagsFromFile); + return allTags; + } - try { - tempTags = new ArrayList<>(tempTagsCache); - - List extends FunctionTag> dbTags = getAllTagsFromDatabase(); - for (FunctionTag tag : dbTags) { - model.addTag(tag); - } - - // Now add any temp tags. Note that this is the point at which we prune the - // temp tag list to remove any tags that have been added to the database. We - // don't do it when the command for the add has been initiated, we do it here, - // in response to getting the latest items from the db directly. - Iterator iter = tempTags.iterator(); - while (iter.hasNext()) { - FunctionTag tag = iter.next(); - Optional extends FunctionTag> foundTag = dbTags.stream() - .filter(t -> t.getName().equals(tag.getName())) - .findAny(); - if (foundTag.isPresent()) { - iter.remove(); - } - else { - model.addTag(tag); - } - } - - model.reload(); - applyFilter(); - table.setFunction(function); - } - catch (IOException e) { - Msg.error(this, "Error retrieving tags", e); - } - } - /** - * Returns true if all tags in the selection are enabled; false - * otherwise + * Returns true if all tags in the selection are enabled; false otherwise * - * @return true if all tags in the selection are enabled; false - * otherwise + * @return true if all tags in the selection are enabled; false otherwise */ public boolean isSelectionEnabled() { - List selectedTags = getSelectedTags(); - List assignedTags = getAssignedTags(function); + Set selectedTags = getSelectedTags(); + Set assignedTags = getAssignedTags(function); if (assignedTags.containsAll(selectedTags)) { return false; } - + return true; } - + /****************************************************************************** * PRIVATE METHODS ******************************************************************************/ @@ -176,9 +138,8 @@ public class SourceTagsPanel extends TagListPanel { * Returns an array of all tags stored in the database. * * @return list of tags - * @throws IOException */ - private List extends FunctionTag> getAllTagsFromDatabase() throws IOException { + private List extends FunctionTag> getAllTagsFromDatabase() { if (program == null) { return Collections.emptyList(); } @@ -191,7 +152,7 @@ public class SourceTagsPanel extends TagListPanel { * * @return the loaded tags */ - private List loadTags() { + private Set loadTags() { return FunctionTagLoader.loadTags(TAG_FILE); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java index b5c8412105..fe8c40938e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java @@ -19,31 +19,39 @@ import java.awt.BorderLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.*; +import java.util.stream.Collectors; import javax.swing.*; +import org.apache.commons.lang3.StringUtils; + import docking.DockingWindowManager; import docking.widgets.OptionDialog; import docking.widgets.dialogs.InputDialog; +import docking.widgets.table.GTable; +import docking.widgets.table.threaded.ThreadedTableModel; import ghidra.app.cmd.function.ChangeFunctionTagCmd; import ghidra.app.cmd.function.DeleteFunctionTagCmd; import ghidra.framework.cmd.Command; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.*; import ghidra.util.Msg; +import ghidra.util.table.GhidraTableFilterPanel; +import ghidra.util.table.GhidraThreadedTablePanel; /** * Base panel for displaying tags in the function tag window. */ public abstract class TagListPanel extends JPanel { + protected PluginTool tool; protected Program program; protected Function function; - protected FunctionTagTable table; - protected PluginTool tool; - protected String filterString = ""; + protected FunctionTagTableModel model; - protected FunctionTagTableModel filteredModel; + protected FunctionTagTable table; + private GhidraTableFilterPanel filterPanel; + private JLabel titleLabel; /** @@ -58,10 +66,23 @@ public abstract class TagListPanel extends JPanel { setLayout(new BorderLayout()); - model = new FunctionTagTableModel("", provider.getTool()); - filteredModel = new FunctionTagTableModel("", provider.getTool()); + model = new FunctionTagTableModel("Function Tags", provider.getTool(), + this::backgroundLoadTags); + GhidraThreadedTablePanel tablePanel = + new GhidraThreadedTablePanel<>(model) { + protected GTable createTable(ThreadedTableModel tm) { + return new FunctionTagTable(model); + } + }; + table = (FunctionTagTable) tablePanel.getTable(); + filterPanel = new GhidraTableFilterPanel<>(table, model); + + titleLabel = new JLabel(title); + titleLabel.setBorder(BorderFactory.createEmptyBorder(3, 5, 0, 0)); + add(titleLabel, BorderLayout.NORTH); + add(tablePanel, BorderLayout.CENTER); + add(filterPanel, BorderLayout.SOUTH); - table = new FunctionTagTable(filteredModel); table.addMouseListener(new MouseAdapter() { @Override @@ -77,77 +98,15 @@ public abstract class TagListPanel extends JPanel { @Override public void mouseClicked(MouseEvent evt) { - FunctionTagTable table = (FunctionTagTable) evt.getSource(); - - if (evt.getClickCount() == 2) { - int row = table.getSelectedRow(); - int nameCol = table.getColumnModel().getColumnIndex("Name"); - String tagName = (String) table.getValueAt(row, nameCol); - FunctionTagTableModel model = (FunctionTagTableModel) table.getModel(); - FunctionTag tag = model.getTag(tagName); - if (tag == null) { - return; - } - - // If the tag is a temporary one, it's not editable. Show a message to the user. - if (tag instanceof FunctionTagTemp) { - Msg.showWarn(this, table, "Tag Not Editable", - "Tag " + "\"" + tag.getName() + "\"" + - " must be added to the program before it can be modified/deleted"); - return; - } - - String[] labels = new String[] { "Name:", "Comment:" }; - String[] init = new String[] { tag.getName(), tag.getComment() }; - - InputDialog dialog = new InputDialog("Edit Tag", labels, init, true, d -> { - String[] results = d.getValues(); - - if (results == null || results.length != 2) { - Msg.error(this, "Error retrieving data from edit dialog"); // shouldn't happen - return false; - } - - String newName = results[0].trim(); - String newComment = results[1].trim(); - - // If the name is empty, show a warning and don't allow it. A user should - // never want to do this. - if (newName.isEmpty()) { - Msg.showWarn(this, table, "Empty Tag Name?", - "Tag name cannot be empty"); - return false; - } - - // Only process the name edit if the name actually changed. - if (!newName.equals(tag.getName())) { - Command cmd = new ChangeFunctionTagCmd(tag.getName(), newName, - ChangeFunctionTagCmd.TAG_NAME_CHANGED); - tool.execute(cmd, program); - } - - // Only process the comment edit if the comment actually changed. - if (!newComment.equals(tag.getComment())) { - Command cmd = new ChangeFunctionTagCmd(tag.getName(), newComment, - ChangeFunctionTagCmd.TAG_COMMENT_CHANGED); - tool.execute(cmd, program); - } - return true; - }); - - DockingWindowManager.showDialog(tool.getActiveWindow(), dialog); - - if (dialog.isCanceled()) { - return; - } + if (evt.getClickCount() != 2) { + return; } + + int row = table.getSelectedRow(); + editRow(row); } }); - titleLabel = new JLabel(title); - titleLabel.setBorder(BorderFactory.createEmptyBorder(3, 5, 0, 0)); - add(titleLabel, BorderLayout.NORTH); - add(new JScrollPane(table), BorderLayout.CENTER); } /****************************************************************************** @@ -155,12 +114,87 @@ public abstract class TagListPanel extends JPanel { ******************************************************************************/ /** - * Clears the list and repopulates it with a new data set. Clients should override this + * Clears the list and re-populates it with a new data set. Clients should override this * to retrieve data for the given function. * - * @param function the currently selected function in the listing + * @param newFunction the currently selected function in the listing */ - public abstract void refresh(Function function); + public abstract void refresh(Function newFunction); + + /** + * Called by a background thread to load the tags for this panel + * @return the tags + */ + protected abstract Set backgroundLoadTags(); + + void editRow(int row) { + + FunctionTagRowObject rowObject = model.getRowObject(row); + if (rowObject.isImmutable()) { + Msg.showWarn(this, table, "Tag Not Editable", + "Tag " + "\"" + rowObject.getName() + "\"" + + " must be added to the program before it can be modified/deleted"); + return; + } + + String tagName = rowObject.getName(); + String comment = rowObject.getComment(); + String[] labels = new String[] { "Name:", "Comment:" }; + String[] init = new String[] { tagName, comment }; + InputDialog dialog = new InputDialog("Edit Tag", labels, init, true, d -> { + String[] results = d.getValues(); + if (results == null || results.length != 2) { + return false; + } + + String newName = results[0].trim(); + if (StringUtils.isBlank(newName)) { + d.setStatusText("Tag name cannot be empty"); + return false; + } + + if (!Objects.equals(tagName, newName)) { + return true; + } + + String newComment = results[1].trim(); + if (!Objects.equals(comment, newComment)) { + return true; + } + return false; + }); + + DockingWindowManager.showDialog(tool.getActiveWindow(), dialog); + if (dialog.isCanceled()) { + return; + } + + String[] results = dialog.getValues(); + String newName = results[0].trim(); + String newComment = results[1].trim(); + + // Only process the name edit if the name actually changed. + if (!newName.equals(tagName)) { + Command cmd = new ChangeFunctionTagCmd(tagName, newName, + ChangeFunctionTagCmd.TAG_NAME_CHANGED); + tool.execute(cmd, program); + } + + // Only process the comment edit if the comment actually changed. + if (!newComment.equals(comment)) { + Command cmd = new ChangeFunctionTagCmd(tagName, newComment, + ChangeFunctionTagCmd.TAG_COMMENT_CHANGED); + tool.execute(cmd, program); + } + } + + FunctionTagTableModel getModel() { + return model; + } + + FunctionTagTable getTable() { + return table; + } public void clearSelection() { table.clearSelection(); @@ -169,12 +203,6 @@ public abstract class TagListPanel extends JPanel { public void setProgram(Program program) { this.program = program; model.setProgram(program); - filteredModel.setProgram(program); - } - - public void setFilterText(String text) { - filterString = text; - applyFilter(); } public void setTitle(String title) { @@ -188,7 +216,7 @@ public abstract class TagListPanel extends JPanel { * @return true if the tag exists */ public boolean tagExists(String name) { - return model.isTagInModel(name); + return model.containsTag(name); } /****************************************************************************** @@ -211,17 +239,8 @@ public abstract class TagListPanel extends JPanel { * @return true if list contains an immutable tag */ protected boolean isSelectionImmutable() { - int[] selectedRows = table.getSelectedRows(); - int nameCol = table.getColumnModel().getColumnIndex("Name"); - for (int selectedRow : selectedRows) { - String tagName = (String) table.getValueAt(selectedRow, nameCol); - FunctionTag tag = filteredModel.getTag(tagName); - if (tag instanceof FunctionTagTemp) { - return true; - } - } - - return false; + List items = filterPanel.getSelectedItems(); + return items.stream().anyMatch(row -> row.isImmutable()); } /** @@ -229,8 +248,7 @@ public abstract class TagListPanel extends JPanel { */ protected void deleteSelectedTags() { - List selectedTags = getSelectedTags(); - + Set selectedTags = getSelectedTags(); if (selectedTags.isEmpty()) { return; } @@ -241,35 +259,12 @@ public abstract class TagListPanel extends JPanel { "Are you sure? \nThis will delete the tag from all functions in the program.", "OK", OptionDialog.WARNING_MESSAGE); - switch (option) { - case OptionDialog.OPTION_ONE: - for (FunctionTag tag : selectedTags) { - Command cmd = new DeleteFunctionTagCmd(tag.getName()); - tool.execute(cmd, program); - } - break; - case OptionDialog.CANCEL_OPTION: - // do nothing - break; - } - } - - /** - * Filters the list with the current filter settings - */ - protected void applyFilter() { - filteredModel.clear(); - - for (FunctionTag tag : model.getTags()) { - if (filterString.isEmpty()) { - filteredModel.addTag(tag); - } - else if (tag.getName().toLowerCase().contains(filterString.toLowerCase())) { - filteredModel.addTag(tag); + if (option == OptionDialog.OPTION_ONE) { + for (FunctionTag tag : selectedTags) { + Command cmd = new DeleteFunctionTagCmd(tag.getName()); + tool.execute(cmd, program); } } - - filteredModel.reload(); } /** @@ -278,8 +273,8 @@ public abstract class TagListPanel extends JPanel { * @param func the function to get tags for * @return list of all tags assigned to this function */ - protected List getAssignedTags(Function func) { - List assignedTags = new ArrayList<>(); + protected Set getAssignedTags(Function func) { + Set assignedTags = new HashSet<>(); if (func != null) { assignedTags.addAll(func.getTags()); } @@ -291,18 +286,8 @@ public abstract class TagListPanel extends JPanel { * * @return the list of function tags */ - protected List getSelectedTags() { - List tags = new ArrayList<>(); - int[] selectedIndices = table.getSelectedRows(); - for (int i : selectedIndices) { - String tagName = (String) filteredModel.getValueAt(i, 0); - Optional tag = - filteredModel.getTags().stream().filter(t -> t.getName().equals(tagName)).findAny(); - if (tag.isPresent()) { - tags.add(tag.get()); - } - } - - return tags; + protected Set getSelectedTags() { + List items = filterPanel.getSelectedItems(); + return items.stream().map(row -> row.getTag()).collect(Collectors.toSet()); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java index 646f059cfe..d318a58b24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java @@ -15,8 +15,7 @@ */ package ghidra.app.plugin.core.function.tags; -import java.util.Collections; -import java.util.List; +import java.util.Set; import ghidra.app.cmd.function.RemoveFunctionTagCmd; import ghidra.framework.cmd.Command; @@ -40,7 +39,7 @@ public class TargetTagsPanel extends TagListPanel { public TargetTagsPanel(FunctionTagsComponentProvider provider, PluginTool tool, String title) { super(provider, tool, title); - + table.setDisabled(false); } @@ -49,35 +48,33 @@ public class TargetTagsPanel extends TagListPanel { ******************************************************************************/ @Override - public void refresh(Function function) { - + public void refresh(Function newFunction) { + model.clear(); - - this.function = function; - + + this.function = newFunction; + if (function == null) { setTitle("No Function Selected"); } else { - setTitle(function.getName() + " " + "(" + function.getEntryPoint().toString() + ")"); + setTitle(function.getName() + " (" + function.getEntryPoint().toString() + ')'); } - List assignedTags = getAssignedTags(function); - Collections.sort(assignedTags); - for (FunctionTag tag : assignedTags) { - model.addTag(tag); - } - - model.reload(); - applyFilter(); table.setFunction(function); + model.reload(); + } + + @Override + protected Set backgroundLoadTags() { + return getAssignedTags(function); } /** * Removes selected tags from the currently-selected function. */ public void removeSelectedTags() { - List selectedTags = getSelectedTags(); + Set selectedTags = getSelectedTags(); for (FunctionTag tag : selectedTags) { Command cmd = new RemoveFunctionTagCmd(tag.getName(), function.getEntryPoint()); tool.execute(cmd, program); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagEditorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagEditorTest.java index a77c6a53a0..0dedb545dc 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagEditorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagEditorTest.java @@ -15,43 +15,30 @@ */ package ghidra.app.plugin.core.function.tags; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.awt.Dimension; import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import javax.swing.AbstractButton; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import docking.ActionContext; import docking.action.DockingActionIf; +import docking.widgets.dialogs.InputDialog; import docking.widgets.textfield.HintTextField; -import ghidra.app.cmd.function.ChangeFunctionTagCmd; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.gotoquery.GoToServicePlugin; import ghidra.app.services.ProgramManager; -import ghidra.framework.cmd.Command; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.function.FunctionDB; import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.model.address.Address; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.FunctionTag; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; -import ghidra.test.AbstractGhidraHeadedIntegrationTest; -import ghidra.test.ClassicSampleX86ProgramBuilder; -import ghidra.test.TestEnv; +import ghidra.test.*; import ghidra.util.exception.UsrException; /** @@ -82,10 +69,6 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // The UI we're testing. private FunctionTagsComponentProvider provider = null; - /**************************************************************************************** - * SETUP/TEARDOWN - ****************************************************************************************/ - @Before public void setUp() throws Exception { @@ -138,7 +121,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { cb.goTo(new ProgramLocation(program, NON_FUNCTION_ADDRESS)); assertEquals(NON_FUNCTION_ADDRESS, cb.getCurrentAddress()); ActionContext actionContext = cb.getProvider().getActionContext(null); - assertTrue(!editFunctionTags.isEnabledForContext(actionContext)); + assertFalse(editFunctionTags.isEnabledForContext(actionContext)); cb.goToField(FUNCTION_ENTRY_ADDRESS, "Function Signature", 0, 0); assertEquals(FUNCTION_ENTRY_ADDRESS, cb.getCurrentAddress()); @@ -164,7 +147,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // Get an immutable tag from the source panel and set it to be selected. Verify that // the delete button is disabled. - FunctionTagTemp immutableTag = getImmutableTag(); + InMemoryFunctionTag immutableTag = getImmutableTag(); assertTrue("Must have at least one immutable tag for this test", immutableTag != null); selectTagInList(immutableTag.getName(), table); waitForSwing(); @@ -175,7 +158,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { String tagName = "TAG 1"; createTag(tagName); waitForSwing(); - table.addRowSelectionInterval(0, table.getRowCount()-1); + table.addRowSelectionInterval(0, table.getRowCount() - 1); waitForSwing(); assertFalse(isButtonEnabled("deleteBtn")); @@ -197,7 +180,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { FunctionTagTable table = getTagListInPanel("targetPanel"); // Get an immutable tag from the source panel. - FunctionTagTemp tag = getImmutableTag(); + InMemoryFunctionTag tag = getImmutableTag(); assertTrue("Must have at least one immutable tag for this test", tag != null); // Assign the tag to a function, select the tag in the target panel, @@ -205,7 +188,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { addTagToFunction(tag.getName(), FUNCTION_ENTRY_ADDRESS); waitForSwing(); - boolean inList = isTagNameInList(tag.getName(), getAllTags()); + boolean inList = tagExists(tag.getName(), getAllTags()); assertTrue(inList); selectTagInList(tag.getName(), table); @@ -232,18 +215,18 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // First verify that our function does not already contain the tags we're going // to add. Collection extends FunctionTag> tags = getAllTags(); - assertTrue(!isTagNameInList(tagName1, tags)); - assertTrue(!isTagNameInList(tagName2, tags)); - assertTrue(!isTagNameInList(tagName3, tags)); + assertFalse(tagExists(tagName1, tags)); + assertFalse(tagExists(tagName2, tags)); + assertFalse(tagExists(tagName3, tags)); // Now add them. createTag(tagName1 + ", " + tagName2 + " ,, " + tagName3); // Check the database to verify that the tags were added correctly. tags = getAllTags(); - assertTrue(isTagNameInList(tagName1, tags)); - assertTrue(isTagNameInList(tagName2, tags)); - assertTrue(isTagNameInList(tagName3, tags)); + assertTrue(tagExists(tagName1, tags)); + assertTrue(tagExists(tagName2, tags)); + assertTrue(tagExists(tagName3, tags)); } /** @@ -258,10 +241,10 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // First add a tag (so we have something to delete). createTag(name); - assertTrue(isTagNameInList(name, getAllTags())); + assertTrue(tagExists(name, getAllTags())); deleteTag(name); - assertTrue(!isTagNameInList(name, getAllTags())); + assertFalse(tagExists(name, getAllTags())); } /** @@ -277,14 +260,14 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // Add a tag and verify that it was correctly added to the database. createTag(oldName); - assertTrue(isTagNameInList(oldName, getAllTags())); + assertTrue(tagExists(oldName, getAllTags())); // Update the tag name. updateTagName(oldName, newName); // Verify that the old name is no longer in the db, but the new name is. - assertTrue(!isTagNameInList(oldName, getAllTags())); - assertTrue(isTagNameInList(newName, getAllTags())); + assertFalse(tagExists(oldName, getAllTags())); + assertTrue(tagExists(newName, getAllTags())); } /** @@ -297,7 +280,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { String name = "TEST"; createTag(name); addTagToFunction(name, FUNCTION_ENTRY_ADDRESS); - assertTrue(isTagNameInList(name, getTagsForFunctionAt(FUNCTION_ENTRY_ADDRESS))); + assertTrue(tagExists(name, getTags(FUNCTION_ENTRY_ADDRESS))); } /** @@ -310,7 +293,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { createTag(name); addTagToFunction(name, FUNCTION_ENTRY_ADDRESS); removeTagFromFunction(name, FUNCTION_ENTRY_ADDRESS); - assertTrue(!isTagNameInList(name, getTagsForFunctionAt(FUNCTION_ENTRY_ADDRESS))); + assertFalse(tagExists(name, getTags(FUNCTION_ENTRY_ADDRESS))); } /** @@ -324,7 +307,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // Verify that the function panel is initially empty AllFunctionsPanel functionsPanel = getFunctionsPanel(); List functions = functionsPanel.getFunctions(); - assert (functions.isEmpty()); + assertTrue(functions.isEmpty()); // Create a new tag and add it to a function String tagName1 = "TAG 1"; @@ -340,9 +323,9 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // we have exactly 1 match, and that the address is for the correct // function) functions = functionsPanel.getFunctions(); - assert (functions.size() == 1); + assertTrue(functions.size() == 1); Function f = functions.get(0); - assert (f.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS)); + assertTrue(f.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS)); } /** @@ -357,7 +340,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // Verify that the function panel is initially empty AllFunctionsPanel functionsPanel = getFunctionsPanel(); List functions = functionsPanel.getFunctions(); - assert (functions.isEmpty()); + assertTrue(functions.isEmpty()); // Create a new tag and add it to both functions String tagName1 = "TAG 1"; @@ -374,11 +357,11 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // we have exactly 2 matches, and that the addresses are for the correct // functions) functions = functionsPanel.getFunctions(); - assert (functions.size() == 2); + assertTrue(functions.size() == 2); Function f1 = functions.get(0); Function f2 = functions.get(1); - assert (f1.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS)); - assert (f2.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_2)); + assertTrue(f1.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS)); + assertTrue(f2.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_2)); } /** @@ -393,7 +376,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // Verify that the function panel is initially empty AllFunctionsPanel functionsPanel = getFunctionsPanel(); List functions = functionsPanel.getFunctions(); - assert (functions.isEmpty()); + assertTrue(functions.isEmpty()); // Create two new tags and add them to the functions String tagName1 = "TAG 1"; @@ -404,27 +387,27 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { createTag(tagName2); addTagToFunction(tagName2, FUNCTION_ENTRY_ADDRESS_2); addTagToFunction(tagName2, FUNCTION_ENTRY_ADDRESS_3); - + // Select both tags and verify that 3 functions are listed in // the functions panel goTo(tool, program, addr("00000000")); FunctionTagTable table = getTagListInPanel("sourcePanel"); selectTagInList(tagName1, table); int index = table.getSelectedRow(); - table.addRowSelectionInterval(index, index+1); + table.addRowSelectionInterval(index, index + 1); clickTableRange(table, index, 2); - + waitForTableModel(functionsPanel.getTableModel()); // Verify that all 3 functions are in the function panel functions = functionsPanel.getFunctions(); - assert (functions.size() == 3); + assertTrue(functions.size() == 3); Function f1 = functions.get(0); Function f2 = functions.get(1); Function f3 = functions.get(2); - assert (f1.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS)); - assert (f2.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_2)); - assert (f3.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_3)); + assertTrue(f1.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS)); + assertTrue(f2.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_2)); + assertTrue(f3.getEntryPoint().equals(FUNCTION_ENTRY_ADDRESS_3)); } /**************************************************************************************** @@ -442,10 +425,33 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * @param newName the new tag name */ private void updateTagName(String oldName, String newName) { - Command cmd = - new ChangeFunctionTagCmd(oldName, newName, ChangeFunctionTagCmd.TAG_NAME_CHANGED); - tool.execute(cmd, program); + int row = getRow(oldName); + SourceTagsPanel sourcePanel = provider.getSourcePanel(); + runSwing(() -> sourcePanel.editRow(row), false); + + InputDialog dialog = waitForDialogComponent(InputDialog.class); + runSwing(() -> { + dialog.setValue(newName, 0); + dialog.setValue("Some new comment", 1); + }); + pressButtonByText(dialog, "OK"); + waitForSwing(); + } + + private int getRow(String tagName) { + SourceTagsPanel sourcePanel = provider.getSourcePanel(); + FunctionTagTableModel model = sourcePanel.getModel(); + int n = model.getRowCount(); + for (int row = 0; row < n; row++) { + FunctionTagRowObject rowObject = model.getRowObject(row); + if (rowObject.getName().equals(tagName)) { + return row; + } + } + + fail("Could not find row for '" + tagName + "'"); + return -1; } /** @@ -523,16 +529,13 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * @throws Exception if there is a problem clicking cells in a list */ private void selectTagInList(String name, FunctionTagTable table) throws Exception { - FunctionTag tag = getListItemByName(name, table); - if (tag == null) { - throw new UsrException("Error retrieving tag with name: " + name); - } - + assertTagExists(name, table); + int row = 0; - for (int i=0; i tag.delete()); + waitForTables(); waitForSwing(); } @@ -661,7 +666,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * @param addr the function entry point * @return set of tags or null if function not found */ - private Set getTagsForFunctionAt(Address addr) { + private Set getTags(Address addr) { FunctionDB function = (FunctionDB) program.getFunctionManager().getFunctionContaining(addr); if (function == null) { @@ -684,16 +689,15 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * @return an immutable tag, or null if not found * @throws UsrException if there's an error retrieving tags in the source panel */ - private FunctionTagTemp getImmutableTag() throws UsrException { + private InMemoryFunctionTag getImmutableTag() throws UsrException { FunctionTagTable table = getTagListInPanel("sourcePanel"); - FunctionTagTableModel model = (FunctionTagTableModel)table.getModel(); - Optional foundTag = model.getTags().stream().filter(t -> t instanceof FunctionTagTemp).findAny(); - if (foundTag.isPresent()) { - return (FunctionTagTemp)foundTag.get(); - } - - return null; + FunctionTagTableModel model = (FunctionTagTableModel) table.getModel(); + Optional optional = + model.getModelData().stream().filter(row -> row.isImmutable()).findAny(); + assertTrue("No Immutable tags found", optional.isPresent()); + FunctionTag foundTag = optional.get().getTag(); + return (InMemoryFunctionTag) foundTag; } /** @@ -713,7 +717,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * @param tags the list to inspect * @return true if found */ - private boolean isTagNameInList(String name, Collection extends FunctionTag> tags) { + private boolean tagExists(String name, Collection extends FunctionTag> tags) { FunctionTag tag = getTagForName(name, tags); return tag != null; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagLoaderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagLoaderTest.java index 0afa12fc2e..546162dd5f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagLoaderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagLoaderTest.java @@ -15,13 +15,13 @@ */ package ghidra.app.plugin.core.function.tags; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import org.junit.Test; @@ -117,9 +117,9 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { public void testLoadTags_EmptyFile() throws Exception { // Create file without contents File xxeFile = createTempFileForTest(); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); + Set expectedTags = new HashSet<>(); assertEquals(tags, expectedTags); } @@ -128,9 +128,9 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { // Create file with contents File xxeFile = createTempFileForTest(); Files.write(xxeFile.toPath(), FUNCTION_TAGS_EMPTY_TAGS.getBytes()); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); + Set expectedTags = new HashSet<>(); assertEquals(tags, expectedTags); } @@ -140,9 +140,9 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { File xxeFile = createTempFileForTest(); xxeFile.delete(); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); + Set expectedTags = new HashSet<>(); assertEquals(tags, expectedTags); } @@ -151,9 +151,9 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { // Create file with contents File xxeFile = createTempFileForTest(); Files.write(xxeFile.toPath(), FUNCTION_TAGS_MALFORMED_XML.getBytes()); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); + Set expectedTags = new HashSet<>(); assertEquals(tags, expectedTags); } @@ -168,17 +168,17 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { // Create file with contents File xxeFile = createTempFileForTest(); Files.write(xxeFile.toPath(), FUNCTION_TAGS_DEFAULT.getBytes()); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); - expectedTags.add(new FunctionTagTemp("COMPRESSION", "")); - expectedTags.add(new FunctionTagTemp("CONSTRUCTOR", "")); - expectedTags.add(new FunctionTagTemp("CRYPTO", "")); - expectedTags.add(new FunctionTagTemp("DESTRUCTOR", "")); - expectedTags.add(new FunctionTagTemp("IO", "")); - expectedTags.add(new FunctionTagTemp("LIBRARY", "")); - expectedTags.add(new FunctionTagTemp("NETWORK", "")); - expectedTags.add(new FunctionTagTemp("UNPACKER", "")); + Set expectedTags = new HashSet<>(); + expectedTags.add(new InMemoryFunctionTag("COMPRESSION", "")); + expectedTags.add(new InMemoryFunctionTag("CONSTRUCTOR", "")); + expectedTags.add(new InMemoryFunctionTag("CRYPTO", "")); + expectedTags.add(new InMemoryFunctionTag("DESTRUCTOR", "")); + expectedTags.add(new InMemoryFunctionTag("IO", "")); + expectedTags.add(new InMemoryFunctionTag("LIBRARY", "")); + expectedTags.add(new InMemoryFunctionTag("NETWORK", "")); + expectedTags.add(new InMemoryFunctionTag("UNPACKER", "")); assertEquals(tags, expectedTags); } @@ -189,16 +189,16 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { // Create file with contents File xxeFile = createTempFileForTest(); Files.write(xxeFile.toPath(), FUNCTION_TAGS_HAS_BLANK_NAME_VALUE.getBytes()); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); - expectedTags.add(new FunctionTagTemp("COMPRESSION", "")); - expectedTags.add(new FunctionTagTemp("CONSTRUCTOR", "")); - expectedTags.add(new FunctionTagTemp("CRYPTO", "")); - expectedTags.add(new FunctionTagTemp("IO", "")); - expectedTags.add(new FunctionTagTemp("LIBRARY", "")); - expectedTags.add(new FunctionTagTemp("NETWORK", "")); - expectedTags.add(new FunctionTagTemp("UNPACKER", "")); + Set expectedTags = new HashSet<>(); + expectedTags.add(new InMemoryFunctionTag("COMPRESSION", "")); + expectedTags.add(new InMemoryFunctionTag("CONSTRUCTOR", "")); + expectedTags.add(new InMemoryFunctionTag("CRYPTO", "")); + expectedTags.add(new InMemoryFunctionTag("IO", "")); + expectedTags.add(new InMemoryFunctionTag("LIBRARY", "")); + expectedTags.add(new InMemoryFunctionTag("NETWORK", "")); + expectedTags.add(new InMemoryFunctionTag("UNPACKER", "")); assertEquals(tags, expectedTags); } @@ -209,17 +209,17 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { // Create file with contents File xxeFile = createTempFileForTest(); Files.write(xxeFile.toPath(), FUNCTION_TAGS_HAS_COMMENT_VALUE.getBytes()); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); - expectedTags.add(new FunctionTagTemp("COMPRESSION", "")); - expectedTags.add(new FunctionTagTemp("CONSTRUCTOR", "")); - expectedTags.add(new FunctionTagTemp("CRYPTO", "")); - expectedTags.add(new FunctionTagTemp("DESTRUCTOR", "IM A COMMENT")); - expectedTags.add(new FunctionTagTemp("IO", "")); - expectedTags.add(new FunctionTagTemp("LIBRARY", "")); - expectedTags.add(new FunctionTagTemp("NETWORK", "")); - expectedTags.add(new FunctionTagTemp("UNPACKER", "")); + Set expectedTags = new HashSet<>(); + expectedTags.add(new InMemoryFunctionTag("COMPRESSION", "")); + expectedTags.add(new InMemoryFunctionTag("CONSTRUCTOR", "")); + expectedTags.add(new InMemoryFunctionTag("CRYPTO", "")); + expectedTags.add(new InMemoryFunctionTag("DESTRUCTOR", "IM A COMMENT")); + expectedTags.add(new InMemoryFunctionTag("IO", "")); + expectedTags.add(new InMemoryFunctionTag("LIBRARY", "")); + expectedTags.add(new InMemoryFunctionTag("NETWORK", "")); + expectedTags.add(new InMemoryFunctionTag("UNPACKER", "")); assertEquals(tags, expectedTags); } @@ -230,17 +230,17 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { // Create file with contents File xxeFile = createTempFileForTest(); Files.write(xxeFile.toPath(), FUNCTION_TAGS_NO_COMMENT_TAG.getBytes()); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); - expectedTags.add(new FunctionTagTemp("COMPRESSION", "")); - expectedTags.add(new FunctionTagTemp("CONSTRUCTOR", "")); - expectedTags.add(new FunctionTagTemp("CRYPTO", "")); - expectedTags.add(new FunctionTagTemp("DESTRUCTOR", "")); - expectedTags.add(new FunctionTagTemp("IO", "")); - expectedTags.add(new FunctionTagTemp("LIBRARY", "")); - expectedTags.add(new FunctionTagTemp("NETWORK", "")); - expectedTags.add(new FunctionTagTemp("UNPACKER", "")); + Set expectedTags = new HashSet<>(); + expectedTags.add(new InMemoryFunctionTag("COMPRESSION", "")); + expectedTags.add(new InMemoryFunctionTag("CONSTRUCTOR", "")); + expectedTags.add(new InMemoryFunctionTag("CRYPTO", "")); + expectedTags.add(new InMemoryFunctionTag("DESTRUCTOR", "")); + expectedTags.add(new InMemoryFunctionTag("IO", "")); + expectedTags.add(new InMemoryFunctionTag("LIBRARY", "")); + expectedTags.add(new InMemoryFunctionTag("NETWORK", "")); + expectedTags.add(new InMemoryFunctionTag("UNPACKER", "")); assertEquals(tags, expectedTags); } @@ -257,16 +257,16 @@ public class FunctionTagLoaderTest extends AbstractGhidraHeadedIntegrationTest { // Create file with contents File xxeFile = createTempFileForTest(); Files.write(xxeFile.toPath(), FUNCTION_TAGS_HAS_NO_NAME_VALUE.getBytes()); - List tags = FunctionTagLoader.loadTags(xxeFile); + Set tags = FunctionTagLoader.loadTags(xxeFile); - List expectedTags = new ArrayList<>(); - expectedTags.add(new FunctionTagTemp("COMPRESSION", "")); - expectedTags.add(new FunctionTagTemp("CONSTRUCTOR", "")); - expectedTags.add(new FunctionTagTemp("CRYPTO", "")); - expectedTags.add(new FunctionTagTemp("IO", "")); - expectedTags.add(new FunctionTagTemp("LIBRARY", "")); - expectedTags.add(new FunctionTagTemp("NETWORK", "")); - expectedTags.add(new FunctionTagTemp("UNPACKER", "")); + Set expectedTags = new HashSet<>(); + expectedTags.add(new InMemoryFunctionTag("COMPRESSION", "")); + expectedTags.add(new InMemoryFunctionTag("CONSTRUCTOR", "")); + expectedTags.add(new InMemoryFunctionTag("CRYPTO", "")); + expectedTags.add(new InMemoryFunctionTag("IO", "")); + expectedTags.add(new InMemoryFunctionTag("LIBRARY", "")); + expectedTags.add(new InMemoryFunctionTag("NETWORK", "")); + expectedTags.add(new InMemoryFunctionTag("UNPACKER", "")); assertEquals(tags, expectedTags); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java index 9ff8d037de..fe7a15e21d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/InputDialog.java @@ -245,7 +245,16 @@ public class InputDialog extends DialogComponentProvider { * @param text the text */ public void setValue(String text) { - textFields[0].setText(text); + setValue(text, 0); + } + + /** + * Sets the text of the text field at the given index + * @param text the text + * @param index the index of the text field + */ + public void setValue(String text, int index) { + textFields[index].setText(text); } /** diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/Counter.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/Counter.java new file mode 100644 index 0000000000..c14d210046 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/Counter.java @@ -0,0 +1,29 @@ +/* ### + * 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.util.datastruct; + +/** + * Simple class used to avoid immutable objects and autoboxing when storing changing integer + * primitives in a collection. + */ +public class Counter { + public int count; + + @Override + public String toString() { + return Integer.toString(count); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java index eb7ea7e85f..f372ea3490 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java @@ -16,6 +16,7 @@ package ghidra.program.database.function; import java.io.IOException; +import java.util.Objects; import db.Record; import ghidra.program.database.DBObjectCache; @@ -47,16 +48,18 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { mgr.lock.acquire(); try { checkDeleted(); - - if (comment == null) + + if (comment == null) { comment = ""; - + } + if (!comment.equals(record.getString(FunctionTagAdapter.COMMENT_COL))) { record.setString(FunctionTagAdapter.COMMENT_COL, comment); mgr.updateFunctionTag(this); } - - } catch (IOException e) { + + } + catch (IOException e) { mgr.dbError(e); } finally { @@ -69,17 +72,20 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { mgr.lock.acquire(); try { checkDeleted(); - - if (name == null) + + if (name == null) { name = ""; - + } + if (!name.equals(record.getString(FunctionTagAdapter.NAME_COL))) { record.setString(FunctionTagAdapter.NAME_COL, name); mgr.updateFunctionTag(this); } - } catch (IOException e) { + } + catch (IOException e) { mgr.dbError(e); - } finally { + } + finally { mgr.lock.release(); } } @@ -107,7 +113,7 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { mgr.lock.release(); } } - + /** * Get tag record * @return record @@ -149,8 +155,19 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { } @Override - public int hashCode() { - return (int) key; + public void delete() { + mgr.lock.acquire(); + try { + if (checkIsValid()) { + mgr.doDeleteTag(this); + } + } + catch (IOException e) { + mgr.dbError(e); + } + finally { + mgr.lock.release(); + } } @Override @@ -162,37 +179,41 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { return getComment().compareToIgnoreCase(otherTag.getComment()); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((getComment() == null) ? 0 : getComment().hashCode()); + result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); + return result; + } + @Override public boolean equals(Object obj) { - if ((obj == null) || (!(obj instanceof FunctionTag))) { - return false; - } - if (obj == this) { + if (this == obj) { return true; } - FunctionTag tag = (FunctionTag) obj; - if (!getName().equals(tag.getName())) { + if (obj == null) { return false; } - if (!getComment().equals(tag.getComment())) { + if (!(obj instanceof FunctionTag)) { return false; } + + FunctionTag other = (FunctionTag) obj; + if (!Objects.equals(getComment(), other.getComment())) { + return false; + } + + if (!Objects.equals(getName(), other.getName())) { + return false; + } + return true; } @Override - public void delete() { - mgr.lock.acquire(); - try { - if (checkIsValid()) { - mgr.doDeleteTag(this); - } - } catch (IOException e) { - mgr.dbError(e); - } - finally { - mgr.lock.release(); - } + public String toString() { + return getName(); } } - diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTag.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTag.java index 3d4c0d4edb..932b6a0b68 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTag.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTag.java @@ -17,48 +17,42 @@ package ghidra.program.model.listing; /** * Represents a function tag object that can be associated with - * functions. This maps to the - * {@link ghidra.program.database.function.FunctionTagAdapter FunctionTagAdapter} table. + * functions. This maps to the {@code FunctionTagAdapter} table. */ public interface FunctionTag extends Comparable { /** - * Returns the id of the item. - * - * @return + * Returns the id of the item + * @return the id of the item */ public long getId(); /** - * Returns the tag name. - * - * @return + * Returns the tag name + * @return the tag name */ public String getName(); /** - * Returns the tag comment. - * - * @return + * Returns the tag comment + * @return the tag comment */ public String getComment(); /** - * Sets the name of the tag. - * + * Sets the name of the tag * @param name the tag name */ - void setName(String name); + public void setName(String name); /** - * Sets the comment for this tag. - * + * Sets the comment for this tag * @param comment the tag comment */ - void setComment(String comment); + public void setComment(String comment); /** - * Deletes this tag from the program. + * Deletes this tag from the program */ - void delete(); + public void delete(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java index 4dfabe4591..c824c4a29f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java @@ -15,7 +15,6 @@ */ package ghidra.program.model.listing; -import java.io.IOException; import java.util.List; /** diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java index 088103edfb..7bf45555ba 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java @@ -15,7 +15,7 @@ */ package help.screenshot; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.awt.Rectangle; @@ -26,15 +26,12 @@ import org.junit.Test; import docking.widgets.OptionDialog; import docking.widgets.dialogs.InputDialog; -import ghidra.app.plugin.core.function.tags.FunctionTagButtonPanel; -import ghidra.app.plugin.core.function.tags.FunctionTagTableModel; -import ghidra.app.plugin.core.function.tags.FunctionTagTable; -import ghidra.app.plugin.core.function.tags.FunctionTagsComponentProvider; -import ghidra.app.plugin.core.function.tags.SourceTagsPanel; +import ghidra.app.plugin.core.function.tags.*; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionIterator; import ghidra.program.util.ProgramLocation; +import ghidra.util.Swing; import ghidra.util.exception.UsrException; public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { @@ -80,7 +77,7 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { FunctionTagTable table = (FunctionTagTable) getInstanceField("table", sourcePanel); Rectangle bounds = table.getCellRect(7, 0, false); // Cell 7 is an editable item doubleClick(table, bounds.x, bounds.y); - + InputDialog warningDialog = waitForDialogComponent(InputDialog.class); captureDialog(warningDialog); @@ -138,7 +135,7 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { FunctionTagTableModel model = (FunctionTagTableModel) table.getModel(); int row = -1; - for (int i=0; i { + provider.programActivated(program); + navigateToFunction(provider); + }); - JTextField inputField = (JTextField) getInstanceField("tagInputTF", provider); + JTextField inputField = (JTextField) getInstanceField("tagInputField", provider); setText(inputField, "Tag 2, Tag 3"); triggerEnter(inputField);