mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-5474 - Symbol Tree - Event handling improvements to maintain user view position; added an option for org node group threshold; Fixed missing nodes under classes
This commit is contained in:
parent
397a814f5f
commit
5f17963eba
21 changed files with 1053 additions and 157 deletions
|
@ -114,7 +114,7 @@ public class DisconnectedSymbolTreeProvider extends SymbolTreeProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SymbolTreeRootNode createRootNode() {
|
protected SymbolTreeRootNode createRootNode() {
|
||||||
return new ConfigurableSymbolTreeRootNode(program);
|
return new ConfigurableSymbolTreeRootNode(program, getNodeGroupThreshold());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -22,12 +22,14 @@ import ghidra.app.CorePluginPackage;
|
||||||
import ghidra.app.events.*;
|
import ghidra.app.events.*;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.services.GoToService;
|
import ghidra.app.services.GoToService;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.util.HelpLocation;
|
||||||
|
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
|
@ -36,8 +38,8 @@ import ghidra.program.util.ProgramLocation;
|
||||||
category = PluginCategoryNames.COMMON,
|
category = PluginCategoryNames.COMMON,
|
||||||
shortDescription = "Symbol Tree",
|
shortDescription = "Symbol Tree",
|
||||||
description = "This plugin shows the symbols from the program " +
|
description = "This plugin shows the symbols from the program " +
|
||||||
"in a tree hierarchy. All symbols (except for the global namespace symbol)" +
|
"in a tree hierarchy. All symbols (except for the global namespace symbol) " +
|
||||||
" have a parent symbol. From the tree, symbols can be renamed, deleted, or " +
|
"have a parent symbol. From the tree, symbols can be renamed, deleted, or " +
|
||||||
"reorganized.",
|
"reorganized.",
|
||||||
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class },
|
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class },
|
||||||
servicesProvided = { SymbolTreeService.class }
|
servicesProvided = { SymbolTreeService.class }
|
||||||
|
@ -45,7 +47,8 @@ import ghidra.program.util.ProgramLocation;
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
||||||
|
|
||||||
public static final String PLUGIN_NAME = "SymbolTreePlugin";
|
private static final String OPTIONS_CATEGORY = "Symbol Tree";
|
||||||
|
private static final String OPTION_NAME_GROUP_THRESHOLD = "Group Threshold";
|
||||||
|
|
||||||
private SymbolTreeProvider connectedProvider;
|
private SymbolTreeProvider connectedProvider;
|
||||||
private List<SymbolTreeProvider> disconnectedProviders = new ArrayList<>();
|
private List<SymbolTreeProvider> disconnectedProviders = new ArrayList<>();
|
||||||
|
@ -53,8 +56,12 @@ public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
||||||
private GoToService goToService;
|
private GoToService goToService;
|
||||||
private boolean processingGoTo;
|
private boolean processingGoTo;
|
||||||
|
|
||||||
|
private OptionsChangeListener optionsListener = new SymbolTreeOptionsListener();
|
||||||
|
private int nodeGroupThreshold = 200;
|
||||||
|
|
||||||
public SymbolTreePlugin(PluginTool tool) {
|
public SymbolTreePlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
|
||||||
connectedProvider = new SymbolTreeProvider(tool, this);
|
connectedProvider = new SymbolTreeProvider(tool, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +115,20 @@ public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
goToService = tool.getService(GoToService.class);
|
goToService = tool.getService(GoToService.class);
|
||||||
|
|
||||||
|
initializeOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeOptions() {
|
||||||
|
|
||||||
|
ToolOptions options = tool.getOptions(OPTIONS_CATEGORY);
|
||||||
|
options.addOptionsChangeListener(optionsListener);
|
||||||
|
|
||||||
|
HelpLocation help = new HelpLocation("SymbolTreePlugin", "GroupNode");
|
||||||
|
options.registerOption(OPTION_NAME_GROUP_THRESHOLD, nodeGroupThreshold, help,
|
||||||
|
"The max number of children before nodes are organized by name");
|
||||||
|
|
||||||
|
nodeGroupThreshold = options.getInt(OPTION_NAME_GROUP_THRESHOLD, nodeGroupThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -192,4 +213,25 @@ public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
||||||
connectedProvider.selectSymbol(symbol);
|
connectedProvider.selectSymbol(symbol);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getNodeGroupThreshold() {
|
||||||
|
return nodeGroupThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SymbolTreeOptionsListener implements OptionsChangeListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||||
|
Object newValue) throws OptionsVetoException {
|
||||||
|
|
||||||
|
if (OPTION_NAME_GROUP_THRESHOLD.equals(optionName)) {
|
||||||
|
nodeGroupThreshold = (int) newValue;
|
||||||
|
connectedProvider.rebuildTree();
|
||||||
|
for (SymbolTreeProvider provider : disconnectedProviders) {
|
||||||
|
provider.rebuildTree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,31 +75,34 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
* prevent to much work from happening too fast. Also, we perform the work in a bulk task
|
* prevent to much work from happening too fast. Also, we perform the work in a bulk task
|
||||||
* so that the tree can benefit from optimizations made by the bulk task.
|
* so that the tree can benefit from optimizations made by the bulk task.
|
||||||
*/
|
*/
|
||||||
private List<GTreeTask> bufferedTasks = new ArrayList<>();
|
private List<AbstractSymbolUpdateTask> bufferedTasks = new ArrayList<>();
|
||||||
private Map<Program, GTreeState> treeStateMap = new HashMap<>();
|
private Map<Program, GTreeState> treeStateMap = new HashMap<>();
|
||||||
private SwingUpdateManager domainChangeUpdateManager = new SwingUpdateManager(1000,
|
private SwingUpdateManager domainChangeUpdateManager = new SwingUpdateManager(1000,
|
||||||
AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider", () -> {
|
AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider - Bulk Update", () -> {
|
||||||
|
|
||||||
if (bufferedTasks.isEmpty()) {
|
if (bufferedTasks.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bufferedTasks.size() == 1) {
|
List<AbstractSymbolUpdateTask> copiedTasks = new ArrayList<>(bufferedTasks);
|
||||||
//
|
bufferedTasks.clear();
|
||||||
// Single events happen from user operations, like creating namespaces and
|
tree.runTask(new BulkWorkTask(tree, copiedTasks));
|
||||||
// rename operations.
|
});
|
||||||
//
|
|
||||||
// Perform a simple update in the normal fashion (a single, targeted filter
|
// Track the tree state from before undo/redo operations so we can put the user's view back. We
|
||||||
// performed when adding changing one symbol is faster than the complete
|
// buffer this so the user can perform multiple rapid operations without responding to each one.
|
||||||
// refilter done by the bulk task below).
|
private GTreeState preRestoreTreeState;
|
||||||
//
|
private SwingUpdateManager restoredUpdateManager = new SwingUpdateManager(750,
|
||||||
tree.runTask(bufferedTasks.remove(0));
|
AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider - Restore", () -> {
|
||||||
|
|
||||||
|
// trigger a delayed refilter to happen after we restore the tree state
|
||||||
|
tree.refilterLater();
|
||||||
|
if (preRestoreTreeState == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<GTreeTask> copiedTasks = new ArrayList<>(bufferedTasks);
|
tree.restoreTreeState(preRestoreTreeState);
|
||||||
bufferedTasks.clear();
|
preRestoreTreeState = null;
|
||||||
tree.runTask(new BulkWorkTask(tree, copiedTasks));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
public SymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin) {
|
public SymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin) {
|
||||||
|
@ -134,7 +137,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SymbolTreeRootNode createRootNode() {
|
protected SymbolTreeRootNode createRootNode() {
|
||||||
return new SymbolTreeRootNode(program);
|
return new SymbolTreeRootNode(program, getNodeGroupThreshold());
|
||||||
}
|
}
|
||||||
|
|
||||||
private JComponent buildProvider() {
|
private JComponent buildProvider() {
|
||||||
|
@ -307,6 +310,10 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
// Class Methods
|
// Class Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
protected int getNodeGroupThreshold() {
|
||||||
|
return plugin.getNodeGroupThreshold();
|
||||||
|
}
|
||||||
|
|
||||||
GTree getTree() {
|
GTree getTree() {
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
@ -467,9 +474,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
// seems safer to cancel an edit rather than to commit it without asking.
|
// seems safer to cancel an edit rather than to commit it without asking.
|
||||||
tree.cancelEditing();
|
tree.cancelEditing();
|
||||||
|
|
||||||
|
// preserve the original state to handle multiple undo/redo requests
|
||||||
|
if (preRestoreTreeState == null) {
|
||||||
|
preRestoreTreeState = tree.getTreeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
restoredUpdateManager.updateLater();
|
||||||
|
|
||||||
SymbolTreeRootNode node = (SymbolTreeRootNode) tree.getModelRoot();
|
SymbolTreeRootNode node = (SymbolTreeRootNode) tree.getModelRoot();
|
||||||
node.setChildren(null);
|
node.setChildren(null);
|
||||||
tree.refilterLater();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void symbolChanged(Symbol symbol) {
|
private void symbolChanged(Symbol symbol) {
|
||||||
|
@ -488,7 +501,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
addTask(new SymbolRemovedTask(tree, symbol));
|
addTask(new SymbolRemovedTask(tree, symbol));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTask(GTreeTask task) {
|
private void addTask(AbstractSymbolUpdateTask task) {
|
||||||
// Note: if we want to call this method from off the Swing thread, then we have to
|
// Note: if we want to call this method from off the Swing thread, then we have to
|
||||||
// synchronize on the list that we are adding to here.
|
// synchronize on the list that we are adding to here.
|
||||||
Swing.assertSwingThread("Adding tasks must be done on the Swing thread," +
|
Swing.assertSwingThread("Adding tasks must be done on the Swing thread," +
|
||||||
|
@ -621,6 +634,11 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
private void processSymbolChanged(ProgramChangeRecord pcr) {
|
private void processSymbolChanged(ProgramChangeRecord pcr) {
|
||||||
Symbol symbol = (Symbol) pcr.getObject();
|
Symbol symbol = (Symbol) pcr.getObject();
|
||||||
|
Object oldValue = pcr.getOldValue();
|
||||||
|
if (oldValue instanceof Namespace oldNs) {
|
||||||
|
addTask(new SymbolScopeChangedTask(tree, symbol, oldNs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
symbolChanged(symbol);
|
symbolChanged(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -712,12 +730,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
TreePath[] selectionPaths = tree.getSelectionPaths();
|
|
||||||
doRun(monitor);
|
doRun(monitor);
|
||||||
|
|
||||||
if (selectionPaths.length != 0) {
|
|
||||||
tree.setSelectionPaths(selectionPaths);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -739,7 +752,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
// the symbol may have been deleted while we are processing bulk changes
|
// the symbol may have been deleted while we are processing bulk changes
|
||||||
if (!symbol.isDeleted()) {
|
if (!symbol.isDeleted()) {
|
||||||
GTreeNode newNode = rootNode.symbolAdded(symbol);
|
GTreeNode newNode = rootNode.symbolAdded(symbol, monitor);
|
||||||
tree.refilterLater(newNode);
|
tree.refilterLater(newNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -762,9 +775,32 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
// the symbol may have been deleted while we are processing bulk changes
|
// the symbol may have been deleted while we are processing bulk changes
|
||||||
if (!symbol.isDeleted()) {
|
if (!symbol.isDeleted()) {
|
||||||
root.symbolAdded(symbol);
|
SymbolNode newNode = root.symbolAdded(symbol, monitor);
|
||||||
|
tree.refilterLater(newNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SymbolScopeChangedTask extends AbstractSymbolUpdateTask {
|
||||||
|
|
||||||
|
private Namespace oldNamespace;
|
||||||
|
|
||||||
|
SymbolScopeChangedTask(GTree tree, Symbol symbol, Namespace oldNamespace) {
|
||||||
|
super(tree, symbol);
|
||||||
|
this.oldNamespace = oldNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void doRun(TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
|
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
|
||||||
|
root.symbolRemoved(symbol, oldNamespace, monitor);
|
||||||
|
|
||||||
|
// the symbol may have been deleted while we are processing bulk changes
|
||||||
|
if (!symbol.isDeleted()) {
|
||||||
|
SymbolNode newNode = root.symbolAdded(symbol, monitor);
|
||||||
|
tree.refilterLater(newNode);
|
||||||
}
|
}
|
||||||
tree.refilterLater();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,19 +814,21 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
void doRun(TaskMonitor monitor) throws CancelledException {
|
void doRun(TaskMonitor monitor) throws CancelledException {
|
||||||
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
|
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
|
||||||
root.symbolRemoved(symbol, symbol.getName(), monitor);
|
root.symbolRemoved(symbol, symbol.getName(), monitor);
|
||||||
tree.refilterLater();
|
|
||||||
|
// Note: turned this off; less tree flashing seems worth having a parent node still in
|
||||||
|
// the tree that doesn't belong
|
||||||
|
// tree.refilterLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BulkWorkTask extends GTreeBulkTask {
|
private class BulkWorkTask extends GTreeBulkTask {
|
||||||
|
|
||||||
// somewhat arbitrary max amount of work to perform...at some point it is faster to
|
// somewhat arbitrary limit to individual tasks... too many, then reload the tree
|
||||||
// just reload the tree
|
|
||||||
private static final int MAX_TASK_COUNT = 1000;
|
private static final int MAX_TASK_COUNT = 1000;
|
||||||
|
|
||||||
private List<GTreeTask> tasks;
|
private List<AbstractSymbolUpdateTask> tasks;
|
||||||
|
|
||||||
BulkWorkTask(GTree gTree, List<GTreeTask> tasks) {
|
BulkWorkTask(GTree gTree, List<AbstractSymbolUpdateTask> tasks) {
|
||||||
super(gTree);
|
super(gTree);
|
||||||
this.tasks = tasks;
|
this.tasks = tasks;
|
||||||
}
|
}
|
||||||
|
@ -803,10 +841,14 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (GTreeTask task : tasks) {
|
GTreeState state = tree.getTreeState();
|
||||||
|
for (AbstractSymbolUpdateTask task : tasks) {
|
||||||
monitor.checkCancelled();
|
monitor.checkCancelled();
|
||||||
task.run(monitor);
|
task.run(monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GTreeRestoreTreeStateTask task = new GTreeRestoreTreeStateTask(tree, state);
|
||||||
|
task.run(monitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,14 @@ package ghidra.app.plugin.core.symboltree.nodes;
|
||||||
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
import java.awt.datatransfer.DataFlavor;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
||||||
|
import ghidra.app.util.NamespaceUtils;
|
||||||
import ghidra.program.model.listing.GhidraClass;
|
import ghidra.program.model.listing.GhidraClass;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
|
@ -56,7 +58,134 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SymbolNode symbolAdded(Symbol symbol) {
|
protected boolean supportsSymbol(Symbol symbol) {
|
||||||
|
SymbolType symbolType = symbol.getSymbolType();
|
||||||
|
if (symbolType == symbolCategory.getSymbolType()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be in a class at some level
|
||||||
|
Namespace parentNamespace = symbol.getParentNamespace();
|
||||||
|
while (parentNamespace != null && parentNamespace != globalNamespace) {
|
||||||
|
if (parentNamespace instanceof GhidraClass) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
parentNamespace = parentNamespace.getParentNamespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol symbol = key.getSymbol();
|
||||||
|
Namespace parentNs = symbol.getParentNamespace();
|
||||||
|
if (parentNs == globalNamespace) {
|
||||||
|
// no need to search for the class in the tree; the class only lives at the top
|
||||||
|
return findNode(this, key, loadChildren, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set getAllClassNodes() for a description of the map
|
||||||
|
Map<GTreeNode, List<Namespace>> classNodes =
|
||||||
|
getAllClassNodes(symbol, parentNs, loadChildren, monitor);
|
||||||
|
if (classNodes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// since the symbol lives in all of these paths, just pick one in a consistent way
|
||||||
|
List<GTreeNode> keys = new ArrayList<>(classNodes.keySet());
|
||||||
|
Collections.sort(keys);
|
||||||
|
GTreeNode classNode = keys.get(0);
|
||||||
|
List<Namespace> parentPath = classNodes.get(classNode);
|
||||||
|
GTreeNode symbolParent =
|
||||||
|
getNamespaceNode(classNode, parentPath, loadChildren, monitor);
|
||||||
|
return findNode(symbolParent, key, loadChildren, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if (!isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportsSymbol(symbol)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolNode key = SymbolNode.createKeyNode(symbol, symbol.getName(), program);
|
||||||
|
Namespace parentNs = symbol.getParentNamespace();
|
||||||
|
if (parentNs == globalNamespace) {
|
||||||
|
// no need to search for the class in the tree; the class only lives at the top
|
||||||
|
GTreeNode symbolNode = findNode(this, key, false, monitor);
|
||||||
|
if (symbolNode != null) {
|
||||||
|
removeNode(symbolNode);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set getAllClassNodes() for a description of the map
|
||||||
|
Map<GTreeNode, List<Namespace>> classNodes =
|
||||||
|
getAllClassNodes(symbol, oldNamespace, monitor);
|
||||||
|
removeSymbol(key, classNodes, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void symbolRemoved(Symbol symbol, String oldName, TaskMonitor monitor) {
|
||||||
|
if (!isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportsSymbol(symbol)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolNode key = SymbolNode.createKeyNode(symbol, oldName, program);
|
||||||
|
Namespace parentNs = symbol.getParentNamespace();
|
||||||
|
if (parentNs == globalNamespace) {
|
||||||
|
// no need to search for the class in the tree; the class only lives at the top
|
||||||
|
GTreeNode symbolNode = findNode(this, key, false, monitor);
|
||||||
|
if (symbolNode != null) {
|
||||||
|
removeNode(symbolNode);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set getAllClassNodes() for a description of the map
|
||||||
|
Map<GTreeNode, List<Namespace>> classNodes = getAllClassNodes(symbol, parentNs, monitor);
|
||||||
|
removeSymbol(key, classNodes, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSymbol(SymbolNode key, Map<GTreeNode, List<Namespace>> classNodes,
|
||||||
|
TaskMonitor monitor) {
|
||||||
|
|
||||||
|
Set<Entry<GTreeNode, List<Namespace>>> entries = classNodes.entrySet();
|
||||||
|
for (Entry<GTreeNode, List<Namespace>> entry : entries) {
|
||||||
|
|
||||||
|
if (monitor.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start with the the top-level class node and walk the namespace path to find the
|
||||||
|
// parent for the given symbol
|
||||||
|
GTreeNode classNode = entry.getKey();
|
||||||
|
List<Namespace> parentPath = entry.getValue();
|
||||||
|
GTreeNode symbolParent =
|
||||||
|
getNamespaceNode(classNode, parentPath, false, monitor);
|
||||||
|
GTreeNode symbolNode = findNode(symbolParent, key, false, monitor);
|
||||||
|
if (symbolParent != null) {
|
||||||
|
symbolParent.removeNode(symbolNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||||
if (!isLoaded()) {
|
if (!isLoaded()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -66,18 +195,110 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (symbol.getSymbolType() == symbolCategory.getSymbolType()) {
|
if (symbol.getSymbolType() == symbolCategory.getSymbolType()) {
|
||||||
return doAddSymbol(symbol, this); // add new Class symbol
|
doAddSymbol(symbol, this); // add new flat Class symbol
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if the symbol is in a class namespace
|
// set getAllClassNodes() for a description of the map
|
||||||
Namespace parentNamespace = symbol.getParentNamespace();
|
SymbolNode lastNode = null;
|
||||||
Symbol namespaceSymbol = parentNamespace.getSymbol();
|
Namespace parentNs = symbol.getParentNamespace();
|
||||||
SymbolNode key = SymbolNode.createNode(namespaceSymbol, program);
|
Map<GTreeNode, List<Namespace>> classNodes = getAllClassNodes(symbol, parentNs, monitor);
|
||||||
GTreeNode parentNode = findSymbolTreeNode(key, false, TaskMonitor.DUMMY);
|
Set<Entry<GTreeNode, List<Namespace>>> entries = classNodes.entrySet();
|
||||||
if (parentNode == null) {
|
for (Entry<GTreeNode, List<Namespace>> entry : entries) {
|
||||||
return null;
|
|
||||||
|
// start with the the top-level class node and walk the namespace path to find the
|
||||||
|
// parent for the given symbol
|
||||||
|
GTreeNode classNode = entry.getKey();
|
||||||
|
List<Namespace> parentPath = entry.getValue();
|
||||||
|
GTreeNode symbolParent =
|
||||||
|
getNamespaceNode(classNode, parentPath, false, monitor);
|
||||||
|
if (symbolParent != null) {
|
||||||
|
lastNode = doAddSymbol(symbol, symbolParent);
|
||||||
}
|
}
|
||||||
return doAddSymbol(symbol, parentNode);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Uses the namespace path of the given symbol to create a mapping from this Classes category
|
||||||
|
node's top-level child classes to the path from that child node to the given symbol node.
|
||||||
|
|
||||||
|
This mapping allows us to find the symbol in multiple tree paths, such as in this example:
|
||||||
|
|
||||||
|
Classes
|
||||||
|
Class1
|
||||||
|
Label1
|
||||||
|
BarNs
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
|
||||||
|
|
||||||
|
In this tree, the Label2 symbol is in the tree twice. The mapping created by this method
|
||||||
|
will have have as keys both Class1 and Class2. Class1 will be mapped to Class1/BarNs/Class2
|
||||||
|
and Class2 will be mapped to Class2 (since it only has one namespace element).
|
||||||
|
|
||||||
|
This code is needed because this Classes category node will duplicate class nodes. It puts
|
||||||
|
each class at the top-level (as a flattened view) and then also includes each class under
|
||||||
|
any other parent class nodes.
|
||||||
|
|
||||||
|
*/
|
||||||
|
private Map<GTreeNode, List<Namespace>> getAllClassNodes(Symbol symbol, Namespace parentNs,
|
||||||
|
TaskMonitor monitor) {
|
||||||
|
return getAllClassNodes(symbol, parentNs, false, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<GTreeNode, List<Namespace>> getAllClassNodes(Symbol symbol, Namespace parentNs,
|
||||||
|
boolean loadChildren, TaskMonitor monitor) {
|
||||||
|
List<Namespace> parents = NamespaceUtils.getNamespaceParts(parentNs);
|
||||||
|
Map<GTreeNode, List<Namespace>> classByPath = new HashMap<>();
|
||||||
|
findAllClassNodes(this, parents, classByPath, loadChildren, monitor);
|
||||||
|
return classByPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void findAllClassNodes(GTreeNode searchNode, List<Namespace> namespaces,
|
||||||
|
Map<GTreeNode, List<Namespace>> results, boolean loadChildren, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if ((!searchNode.isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namespaces.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace namespace = getNextClass(namespaces);
|
||||||
|
if (namespace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol nsSymbol = namespace.getSymbol();
|
||||||
|
SymbolNode key = SymbolNode.createKeyNode(nsSymbol, nsSymbol.getName(), program);
|
||||||
|
GTreeNode namespaceNode = findNode(searchNode, key, loadChildren, monitor);
|
||||||
|
if (namespaceNode == null) {
|
||||||
|
return; // we hit the last namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namespaceNode instanceof ClassSymbolNode) {
|
||||||
|
List<Namespace> currentPath = new ArrayList<>(namespaces);
|
||||||
|
currentPath.add(0, namespace);
|
||||||
|
results.put(namespaceNode, currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to the next namespace
|
||||||
|
findAllClassNodes(searchNode, namespaces, results, loadChildren, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GhidraClass getNextClass(List<Namespace> namespaces) {
|
||||||
|
while (namespaces.size() > 0) {
|
||||||
|
Namespace ns = namespaces.remove(0);
|
||||||
|
if (ns instanceof GhidraClass) {
|
||||||
|
return (GhidraClass) ns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -99,23 +320,4 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
||||||
Collections.sort(list, getChildrenComparator());
|
Collections.sort(list, getChildrenComparator());
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean supportsSymbol(Symbol symbol) {
|
|
||||||
SymbolType symbolType = symbol.getSymbolType();
|
|
||||||
if (symbolType == symbolCategory.getSymbolType()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// must be in a class at some level
|
|
||||||
Namespace parentNamespace = symbol.getParentNamespace();
|
|
||||||
while (parentNamespace != null && parentNamespace != globalNamespace) {
|
|
||||||
if (parentNamespace instanceof GhidraClass) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
parentNamespace = parentNamespace.getParentNamespace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,8 @@ import ghidra.program.model.listing.Program;
|
||||||
*/
|
*/
|
||||||
public class ConfigurableSymbolTreeRootNode extends SymbolTreeRootNode {
|
public class ConfigurableSymbolTreeRootNode extends SymbolTreeRootNode {
|
||||||
|
|
||||||
public ConfigurableSymbolTreeRootNode(Program program) {
|
public ConfigurableSymbolTreeRootNode(Program program, int groupThreshold) {
|
||||||
super(program);
|
super(program, groupThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void transferSettings(ConfigurableSymbolTreeRootNode otherRoot) {
|
public void transferSettings(ConfigurableSymbolTreeRootNode otherRoot) {
|
||||||
|
|
|
@ -76,6 +76,12 @@ class ExportsCategoryNode extends SymbolCategoryNode {
|
||||||
if (!symbol.isPrimary()) {
|
if (!symbol.isPrimary()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return symbol.isExternalEntryPoint() || symbol.getParentSymbol().isExternalEntryPoint();
|
|
||||||
|
if (symbol.isExternalEntryPoint()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol parent = symbol.getParentSymbol();
|
||||||
|
return parent != null && parent.isExternalEntryPoint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SymbolNode symbolAdded(Symbol symbol) {
|
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||||
if (!isLoaded()) {
|
if (!isLoaded()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -111,15 +111,20 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only allow functions in the global namespace; others live in the Namespaces category
|
||||||
|
if (!isGlobalFunction(symbol)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// variables and parameters will be beneath function nodes, and our parent method
|
// variables and parameters will be beneath function nodes, and our parent method
|
||||||
// will find them
|
// will find them
|
||||||
if (isVariableParameterOrCodeSymbol(symbol)) {
|
if (isVariableParameterOrCodeSymbol(symbol)) {
|
||||||
return super.symbolAdded(symbol);
|
return super.symbolAdded(symbol, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this namespace will be beneath function nodes, and our parent method will find them
|
// this namespace will be beneath function nodes, and our parent method will find them
|
||||||
if (isChildNamespaceOfFunction(symbol)) {
|
if (isChildNamespaceOfFunction(symbol)) {
|
||||||
return super.symbolAdded(symbol);
|
return super.symbolAdded(symbol, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...otherwise, we have a function and we need to add it as a child of our parent node
|
// ...otherwise, we have a function and we need to add it as a child of our parent node
|
||||||
|
@ -128,6 +133,15 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isGlobalFunction(Symbol symbol) {
|
||||||
|
SymbolType type = symbol.getSymbolType();
|
||||||
|
if (type != SymbolType.FUNCTION) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Namespace namespace = symbol.getParentNamespace();
|
||||||
|
return namespace == globalNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isChildNamespaceOfFunction(Symbol symbol) {
|
private boolean isChildNamespaceOfFunction(Symbol symbol) {
|
||||||
if (symbol instanceof Function) {
|
if (symbol instanceof Function) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -151,7 +165,15 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean supportsSymbol(Symbol symbol) {
|
protected boolean supportsSymbol(Symbol symbol) {
|
||||||
if (super.supportsSymbol(symbol)) {
|
if (symbol.isExternal()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we say that we support function symbol types, even though we do not include
|
||||||
|
// functions that live in non-global namespaces. This allows functions that are moved to
|
||||||
|
// be removed from this category.
|
||||||
|
SymbolType type = symbol.getSymbolType();
|
||||||
|
if (type == SymbolType.FUNCTION) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,15 @@ public class LabelCategoryNode extends SymbolCategoryNode {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean supportsSymbol(Symbol symbol) {
|
||||||
|
if (!symbol.isGlobal() || symbol.isExternal()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SymbolType symbolType = symbol.getSymbolType();
|
||||||
|
return symbolType == symbolCategory.getSymbolType();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<GTreeNode> getSymbols(SymbolType type, TaskMonitor monitor)
|
protected List<GTreeNode> getSymbols(SymbolType type, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
|
@ -85,7 +94,7 @@ public class LabelCategoryNode extends SymbolCategoryNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SymbolNode symbolAdded(Symbol symbol) {
|
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||||
if (!isLoaded()) {
|
if (!isLoaded()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,17 @@
|
||||||
package ghidra.app.plugin.core.symboltree.nodes;
|
package ghidra.app.plugin.core.symboltree.nodes;
|
||||||
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
import docking.widgets.tree.GTreeNode;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
||||||
|
import ghidra.app.util.NamespaceUtils;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.Namespace;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.program.model.symbol.Symbol;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class NamespaceCategoryNode extends SymbolCategoryNode {
|
public class NamespaceCategoryNode extends SymbolCategoryNode {
|
||||||
|
|
||||||
|
@ -43,7 +46,13 @@ public class NamespaceCategoryNode extends SymbolCategoryNode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean supportsSymbol(Symbol symbol) {
|
protected boolean supportsSymbol(Symbol symbol) {
|
||||||
if (super.supportsSymbol(symbol)) {
|
|
||||||
|
if (symbol.isExternal()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolType symbolType = symbol.getSymbolType();
|
||||||
|
if (symbolType == SymbolType.NAMESPACE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +61,78 @@ public class NamespaceCategoryNode extends SymbolCategoryNode {
|
||||||
return parentNamespace != null && parentNamespace != globalNamespace;
|
return parentNamespace != null && parentNamespace != globalNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if (!isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportsSymbol(symbol)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Namespace> parents = NamespaceUtils.getNamespaceParts(oldNamespace);
|
||||||
|
GTreeNode namespaceNode = getNamespaceNode(this, parents, false, monitor);
|
||||||
|
if (namespaceNode == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolNode key = SymbolNode.createKeyNode(symbol, symbol.getName(), program);
|
||||||
|
GTreeNode foundNode = findNode(namespaceNode, key, false, monitor);
|
||||||
|
if (foundNode == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GTreeNode foundParent = foundNode.getParent();
|
||||||
|
foundParent.removeNode(foundNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if (!isLoaded()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supportsSymbol(symbol)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
GTreeNode parentNode = this;
|
||||||
|
if (symbol.isGlobal()) {
|
||||||
|
return doAddSymbol(symbol, parentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace parentNamespace = symbol.getParentNamespace();
|
||||||
|
List<Namespace> parents = NamespaceUtils.getNamespaceParts(parentNamespace);
|
||||||
|
GTreeNode namespaceNode = getNamespaceNode(this, parents, false, monitor);
|
||||||
|
if (namespaceNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doAddSymbol(symbol, namespaceNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol symbol = key.getSymbol();
|
||||||
|
Namespace parent = symbol.getParentNamespace();
|
||||||
|
List<Namespace> parents = NamespaceUtils.getNamespaceParts(parent);
|
||||||
|
GTreeNode namespaceNode = getNamespaceNode(this, parents, loadChildren, monitor);
|
||||||
|
if (namespaceNode != null) {
|
||||||
|
return findNode(namespaceNode, key, loadChildren, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// look in the namespace node for the given symbol
|
||||||
|
return findNode(this, key, loadChildren, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsDataFlavors(DataFlavor[] dataFlavors) {
|
public boolean supportsDataFlavors(DataFlavor[] dataFlavors) {
|
||||||
for (DataFlavor flavor : dataFlavors) {
|
for (DataFlavor flavor : dataFlavors) {
|
||||||
|
|
|
@ -243,7 +243,13 @@ public class OrganizationNode extends SymbolTreeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForTooManyNodes() {
|
private void checkForTooManyNodes() {
|
||||||
if (getChildCount() > SymbolCategoryNode.MAX_NODES_BEFORE_CLOSING) {
|
|
||||||
|
SymbolTreeRootNode root = (SymbolTreeRootNode) getRoot();
|
||||||
|
int reOrgLimit = root.getReorganizeLimit();
|
||||||
|
if (getChildCount() < reOrgLimit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If we have too many nodes, find the root category node and close it
|
// If we have too many nodes, find the root category node and close it
|
||||||
GTreeNode parent = getParent();
|
GTreeNode parent = getParent();
|
||||||
while (parent != null) {
|
while (parent != null) {
|
||||||
|
@ -257,7 +263,6 @@ public class OrganizationNode extends SymbolTreeNode {
|
||||||
parent = parent.getParent();
|
parent = parent.getParent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren,
|
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren,
|
||||||
|
|
|
@ -29,8 +29,6 @@ import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||||
public static final int MAX_NODES_BEFORE_ORGANIZING = 100;
|
|
||||||
public static final int MAX_NODES_BEFORE_CLOSING = 200;
|
|
||||||
|
|
||||||
protected SymbolCategory symbolCategory;
|
protected SymbolCategory symbolCategory;
|
||||||
protected SymbolTable symbolTable;
|
protected SymbolTable symbolTable;
|
||||||
|
@ -76,7 +74,9 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||||
SymbolType symbolType = symbolCategory.getSymbolType();
|
SymbolType symbolType = symbolCategory.getSymbolType();
|
||||||
List<GTreeNode> list = getSymbols(symbolType, monitor);
|
List<GTreeNode> list = getSymbols(symbolType, monitor);
|
||||||
monitor.checkCancelled();
|
monitor.checkCancelled();
|
||||||
return OrganizationNode.organize(list, MAX_NODES_BEFORE_ORGANIZING, monitor);
|
SymbolTreeRootNode root = (SymbolTreeRootNode) getRoot();
|
||||||
|
int groupThreshold = root.getNodeGroupThreshold();
|
||||||
|
return OrganizationNode.organize(list, groupThreshold, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Program getProgram() {
|
public Program getProgram() {
|
||||||
|
@ -173,7 +173,9 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||||
dataFlavor == ClassSymbolNode.LOCAL_DATA_FLAVOR;
|
dataFlavor == ClassSymbolNode.LOCAL_DATA_FLAVOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SymbolNode symbolAdded(Symbol symbol) {
|
protected abstract boolean supportsSymbol(Symbol symbol);
|
||||||
|
|
||||||
|
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||||
|
|
||||||
if (!isLoaded()) {
|
if (!isLoaded()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -191,7 +193,7 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||||
Namespace parentNamespace = symbol.getParentNamespace();
|
Namespace parentNamespace = symbol.getParentNamespace();
|
||||||
Symbol namespaceSymbol = parentNamespace.getSymbol();
|
Symbol namespaceSymbol = parentNamespace.getSymbol();
|
||||||
SymbolNode key = SymbolNode.createNode(namespaceSymbol, program);
|
SymbolNode key = SymbolNode.createNode(namespaceSymbol, program);
|
||||||
parentNode = findSymbolTreeNode(key, false, TaskMonitor.DUMMY);
|
parentNode = findSymbolTreeNode(key, false, monitor);
|
||||||
if (parentNode == null) {
|
if (parentNode == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -230,11 +232,17 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
parentNode.addNode(index, newNode);
|
parentNode.addNode(index, newNode);
|
||||||
if (parentNode.isLoaded() && parentNode.getChildCount() > MAX_NODES_BEFORE_CLOSING) {
|
if (!parentNode.isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolTreeRootNode root = (SymbolTreeRootNode) getRoot();
|
||||||
|
int reOrgLimit = root.getReorganizeLimit();
|
||||||
|
if (parentNode.getChildCount() > reOrgLimit) {
|
||||||
GTree tree = parentNode.getTree();
|
GTree tree = parentNode.getTree();
|
||||||
// tree needs to be reorganized, close this category node to clear its children
|
// The tree needs to be reorganized, close this category node to clear its children
|
||||||
// and force a reorganization next time it is opened
|
// and force a reorganization next time it is opened. Also need to clear the selection
|
||||||
// also need to clear the selection so that it doesn't re-open the category
|
// so that it doesn't re-open the category.
|
||||||
tree.clearSelectionPaths();
|
tree.clearSelectionPaths();
|
||||||
tree.runTask(new GTreeCollapseAllTask(tree, parentNode));
|
tree.runTask(new GTreeCollapseAllTask(tree, parentNode));
|
||||||
}
|
}
|
||||||
|
@ -249,6 +257,10 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!supportsSymbol(symbol)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SymbolNode key = SymbolNode.createKeyNode(symbol, oldName, program);
|
SymbolNode key = SymbolNode.createKeyNode(symbol, oldName, program);
|
||||||
GTreeNode foundNode = findSymbolTreeNode(key, false, monitor);
|
GTreeNode foundNode = findSymbolTreeNode(key, false, monitor);
|
||||||
if (foundNode == null) {
|
if (foundNode == null) {
|
||||||
|
@ -259,12 +271,45 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||||
foundParent.removeNode(foundNode);
|
foundParent.removeNode(foundNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean supportsSymbol(Symbol symbol) {
|
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||||
if (!symbol.isGlobal() || symbol.isExternal()) {
|
// Most categories will treat a symbol moved as a remove; symbolAdded() will get called
|
||||||
return false;
|
// after this to restore the symbol. Subclasses that depend on scope will override this
|
||||||
|
// method.
|
||||||
|
symbolRemoved(symbol, monitor);
|
||||||
}
|
}
|
||||||
SymbolType symbolType = symbol.getSymbolType();
|
|
||||||
return symbolType == symbolCategory.getSymbolType();
|
/**
|
||||||
|
* Returns the last Namespace tree node in the given path of namespaces. Each Namespace in the
|
||||||
|
* list from 0 to n will be used to find the last tree node, starting at the given parent
|
||||||
|
* node.
|
||||||
|
*
|
||||||
|
* @param parentNode the node at which to start the search
|
||||||
|
* @param namespaces the list of namespaces to traverse.
|
||||||
|
* @param loadChildren true to load children if they have not been loaded
|
||||||
|
* @param monitor the task monitor
|
||||||
|
* @return the namespace node or null if it is not open in the tree
|
||||||
|
*/
|
||||||
|
protected GTreeNode getNamespaceNode(GTreeNode parentNode, List<Namespace> namespaces,
|
||||||
|
boolean loadChildren, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if (!loadChildren && !parentNode.isLoaded() || monitor.isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namespaces.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace namespace = namespaces.remove(0);
|
||||||
|
Symbol nsSymbol = namespace.getSymbol();
|
||||||
|
SymbolNode key = SymbolNode.createKeyNode(nsSymbol, nsSymbol.getName(), program);
|
||||||
|
GTreeNode namespaceNode = findNode(parentNode, key, loadChildren, monitor);
|
||||||
|
if (namespaceNode == null || namespaces.isEmpty()) {
|
||||||
|
return namespaceNode; // we hit the last namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to the next namespace
|
||||||
|
return getNamespaceNode(namespaceNode, namespaces, loadChildren, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -38,6 +38,8 @@ public class SymbolNode extends SymbolTreeNode {
|
||||||
protected final Program program;
|
protected final Program program;
|
||||||
protected final Symbol symbol;
|
protected final Symbol symbol;
|
||||||
|
|
||||||
|
private String cachedName;
|
||||||
|
|
||||||
private boolean isCut;
|
private boolean isCut;
|
||||||
|
|
||||||
SymbolNode(Program program, Symbol symbol) {
|
SymbolNode(Program program, Symbol symbol) {
|
||||||
|
@ -113,8 +115,11 @@ public class SymbolNode extends SymbolTreeNode {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
if (cachedName == null) {
|
||||||
String baseName = symbol.getName();
|
String baseName = symbol.getName();
|
||||||
return getNameFromBaseName(baseName);
|
cachedName = getNameFromBaseName(baseName);
|
||||||
|
}
|
||||||
|
return cachedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getNameFromBaseName(String baseName) {
|
protected String getNameFromBaseName(String baseName) {
|
||||||
|
|
|
@ -176,18 +176,23 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
||||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||||
|
|
||||||
// if we don't have to loadChildren and we are not loaded get out.
|
// if we don't have to loadChildren and we are not loaded get out.
|
||||||
if (!loadChildren && !isLoaded()) {
|
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see if the given node is the node we want
|
||||||
|
Symbol searchSymbol = key.getSymbol();
|
||||||
|
if (getSymbol() == searchSymbol) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
List<GTreeNode> children = getChildren();
|
List<GTreeNode> children = getChildren();
|
||||||
int index = Collections.binarySearch(children, key, getChildrenComparator());
|
int index = Collections.binarySearch(children, key, getChildrenComparator());
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
GTreeNode node = children.get(index);
|
GTreeNode node = children.get(index);
|
||||||
SymbolTreeNode symbolNode = (SymbolTreeNode) node;
|
SymbolTreeNode symbolNode = (SymbolTreeNode) node;
|
||||||
Symbol searchSymbol = key.getSymbol();
|
|
||||||
if (symbolNode.getSymbol() == searchSymbol) {
|
if (symbolNode.getSymbol() == searchSymbol) {
|
||||||
return node;
|
return symbolNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we know that the given child is not itself a symbol node, but it
|
// At this point we know that the given child is not itself a symbol node, but it
|
||||||
|
@ -199,7 +204,9 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Brute-force lookup in each child. This will not typically be called.
|
// Brute-force lookup in each child. This will not typically be called. Category nodes
|
||||||
|
// that support large numbers of children have overridden this method to perform smarter
|
||||||
|
// searching.
|
||||||
for (GTreeNode childNode : children) {
|
for (GTreeNode childNode : children) {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -217,4 +224,48 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for the given node 'key' inside of the given parent. This method performs an
|
||||||
|
* efficient search and does not recurse below the given node.
|
||||||
|
*
|
||||||
|
* @param parent the node whose children will be searched
|
||||||
|
* @param key the token node to search for
|
||||||
|
* @param loadChildren true to load children; false signals to search only if already loaded
|
||||||
|
* @param monitor the monitor
|
||||||
|
* @return the node or null
|
||||||
|
*/
|
||||||
|
protected GTreeNode findNode(GTreeNode parent, SymbolNode key, boolean loadChildren,
|
||||||
|
TaskMonitor monitor) {
|
||||||
|
|
||||||
|
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if the given node is the node we want
|
||||||
|
Symbol searchSymbol = key.getSymbol();
|
||||||
|
if (parent instanceof SymbolTreeNode symbolNode) {
|
||||||
|
if (symbolNode.getSymbol() == searchSymbol) {
|
||||||
|
return symbolNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Comparator<GTreeNode> comparator = ((SymbolTreeNode) parent).getChildrenComparator();
|
||||||
|
List<GTreeNode> children = parent.getChildren();
|
||||||
|
int index = Collections.binarySearch(children, key, comparator);
|
||||||
|
if (index >= 0) {
|
||||||
|
GTreeNode node = children.get(index);
|
||||||
|
SymbolTreeNode symbolNode = (SymbolTreeNode) node;
|
||||||
|
|
||||||
|
// Some parent nodes may contain OrganizationNodes, which will return as a match when
|
||||||
|
// the symbol does not match. We expect the symbol to always match for clients of this
|
||||||
|
// method.
|
||||||
|
if (symbolNode.getSymbol() == searchSymbol) {
|
||||||
|
return symbolNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,7 @@ import docking.widgets.tree.GTreeNode;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.Symbol;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.program.model.symbol.SymbolType;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class SymbolTreeRootNode extends GTreeNode {
|
public class SymbolTreeRootNode extends GTreeNode {
|
||||||
|
@ -35,8 +34,10 @@ public class SymbolTreeRootNode extends GTreeNode {
|
||||||
|
|
||||||
protected SymbolCategory symbolCategory;
|
protected SymbolCategory symbolCategory;
|
||||||
protected Program program;
|
protected Program program;
|
||||||
|
private int groupThreshold;
|
||||||
|
|
||||||
public SymbolTreeRootNode(Program program) {
|
public SymbolTreeRootNode(Program program, int groupThreshold) {
|
||||||
|
this.groupThreshold = groupThreshold;
|
||||||
this.symbolCategory = SymbolCategory.ROOT_CATEGORY;
|
this.symbolCategory = SymbolCategory.ROOT_CATEGORY;
|
||||||
this.program = program;
|
this.program = program;
|
||||||
|
|
||||||
|
@ -48,6 +49,17 @@ public class SymbolTreeRootNode extends GTreeNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNodeGroupThreshold() {
|
||||||
|
return groupThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReorganizeLimit() {
|
||||||
|
// Arbitrary number to prevent bulk updates from triggering repeated node organization.
|
||||||
|
// The higher the value, the longer the delay between the tree collapsing nodes to signal
|
||||||
|
// that a re-organzation is needed.
|
||||||
|
return groupThreshold * 2;
|
||||||
|
}
|
||||||
|
|
||||||
public Program getProgram() {
|
public Program getProgram() {
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +138,6 @@ public class SymbolTreeRootNode extends GTreeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private GTreeNode findClassSymbol(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
private GTreeNode findClassSymbol(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||||
|
|
||||||
SymbolCategoryNode category = getClassesNode();
|
SymbolCategoryNode category = getClassesNode();
|
||||||
return category.findSymbolTreeNode(key, loadChildren, monitor);
|
return category.findSymbolTreeNode(key, loadChildren, monitor);
|
||||||
}
|
}
|
||||||
|
@ -238,12 +249,12 @@ public class SymbolTreeRootNode extends GTreeNode {
|
||||||
return null; // must be filtered out
|
return null; // must be filtered out
|
||||||
}
|
}
|
||||||
|
|
||||||
public SymbolNode symbolAdded(Symbol symbol) {
|
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||||
SymbolNode returnNode = null;
|
SymbolNode returnNode = null;
|
||||||
List<GTreeNode> allChildren = getChildren();
|
List<GTreeNode> allChildren = getChildren();
|
||||||
for (GTreeNode gNode : allChildren) {
|
for (GTreeNode gNode : allChildren) {
|
||||||
SymbolCategoryNode symbolNode = (SymbolCategoryNode) gNode;
|
SymbolCategoryNode symbolNode = (SymbolCategoryNode) gNode;
|
||||||
SymbolNode newNode = symbolNode.symbolAdded(symbol);
|
SymbolNode newNode = symbolNode.symbolAdded(symbol, monitor);
|
||||||
if (newNode != null) {
|
if (newNode != null) {
|
||||||
returnNode = newNode; // doesn't matter which one we return
|
returnNode = newNode; // doesn't matter which one we return
|
||||||
}
|
}
|
||||||
|
@ -261,6 +272,16 @@ public class SymbolTreeRootNode extends GTreeNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
// we have to loop--the symbol may exist in more than one category
|
||||||
|
List<GTreeNode> allChildren = getChildren();
|
||||||
|
for (GTreeNode gNode : allChildren) {
|
||||||
|
SymbolCategoryNode symbolNode = (SymbolCategoryNode) gNode;
|
||||||
|
symbolNode.symbolRemoved(symbol, oldNamespace, monitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void rebuild() {
|
public void rebuild() {
|
||||||
setChildren(null);
|
setChildren(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the symbol tree plugin.
|
* Tests for the symbol tree plugin.
|
||||||
|
@ -118,7 +119,8 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertTrue(functionsNode.isLoaded());
|
assertTrue(functionsNode.isLoaded());
|
||||||
|
|
||||||
// add lots of nodes to cause functionsNode to close
|
// add lots of nodes to cause functionsNode to close
|
||||||
addFunctions(SymbolCategoryNode.MAX_NODES_BEFORE_CLOSING);
|
int reorganizeLimit = ((SymbolTreeRootNode) rootNode).getReorganizeLimit();
|
||||||
|
addFunctions(reorganizeLimit);
|
||||||
waitForTree(tree);
|
waitForTree(tree);
|
||||||
|
|
||||||
assertFalse(functionsNode.isLoaded());
|
assertFalse(functionsNode.isLoaded());
|
||||||
|
@ -325,6 +327,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
flushAndWaitForTree();
|
flushAndWaitForTree();
|
||||||
|
|
||||||
|
// Functions node
|
||||||
GTreeNode fNode = rootNode.getChild(2);
|
GTreeNode fNode = rootNode.getChild(2);
|
||||||
util.expandNode(fNode);
|
util.expandNode(fNode);
|
||||||
|
|
||||||
|
@ -341,10 +344,13 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertTrue(cutAction.isEnabledForContext(util.getSymbolTreeContext()));
|
assertTrue(cutAction.isEnabledForContext(util.getSymbolTreeContext()));
|
||||||
performAction(cutAction, util.getSymbolTreeContext(), true);
|
performAction(cutAction, util.getSymbolTreeContext(), true);
|
||||||
|
|
||||||
|
// NewNamespace node
|
||||||
GTreeNode gNode = namespaceNode.getChild(0);
|
GTreeNode gNode = namespaceNode.getChild(0);
|
||||||
util.selectNode(gNode);
|
util.selectNode(gNode);
|
||||||
assertTrue(pasteAction.isEnabledForContext(util.getSymbolTreeContext()));
|
assertTrue(pasteAction.isEnabledForContext(util.getSymbolTreeContext()));
|
||||||
|
|
||||||
|
// doStuff function node
|
||||||
|
waitForSwing();
|
||||||
GTreeNode dNode = fNode.getChild(0);
|
GTreeNode dNode = fNode.getChild(0);
|
||||||
util.selectNode(dNode);
|
util.selectNode(dNode);
|
||||||
assertFalse(pasteAction.isEnabledForContext(util.getSymbolTreeContext()));
|
assertFalse(pasteAction.isEnabledForContext(util.getSymbolTreeContext()));
|
||||||
|
@ -775,7 +781,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
Symbol symbol = fNode.getSymbol();
|
Symbol symbol = fNode.getSymbol();
|
||||||
|
|
||||||
// symbolAdded() was throwing an exception before the fix
|
// symbolAdded() was throwing an exception before the fix
|
||||||
symbolRootNode.symbolAdded(symbol);
|
symbolRootNode.symbolAdded(symbol, TaskMonitor.DUMMY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFunctions(int count) throws Exception {
|
private void addFunctions(int count) throws Exception {
|
||||||
|
|
|
@ -30,6 +30,8 @@ import docking.action.DockingActionIf;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import generic.test.AbstractGenericTest;
|
import generic.test.AbstractGenericTest;
|
||||||
|
import ghidra.app.cmd.label.CreateNamespacesCmd;
|
||||||
|
import ghidra.app.cmd.label.RenameLabelCmd;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.marker.MarkerManagerPlugin;
|
import ghidra.app.plugin.core.marker.MarkerManagerPlugin;
|
||||||
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
|
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
|
||||||
|
@ -38,8 +40,8 @@ import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
|
||||||
import ghidra.app.util.viewer.field.*;
|
import ghidra.app.util.viewer.field.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Function;
|
import ghidra.program.model.address.AddressFactory;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
|
@ -313,6 +315,217 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
waitForCondition(tree::isEditing);
|
waitForCondition(tree::isEditing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassCategoryDuplicates_NestedClass_RenameLabel() throws Exception {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
The Classes folder flattens classes so every class appears at the top level. Because
|
||||||
|
users can expand classes, top level classes may also appear nested under other classes.
|
||||||
|
|
||||||
|
Classes
|
||||||
|
Class1
|
||||||
|
Label1
|
||||||
|
BarNs
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
|
||||||
|
Namespaces
|
||||||
|
FooNs
|
||||||
|
Class1
|
||||||
|
Label1
|
||||||
|
BarNs
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
*/
|
||||||
|
|
||||||
|
Namespace fooNs = createNamespace("FooNs");
|
||||||
|
GhidraClass class1 = createClass(fooNs, "Class1");
|
||||||
|
Namespace barNs = createNamespace(class1, "BarNs");
|
||||||
|
createLabel(class1, "Label1", "0x1001100");
|
||||||
|
GhidraClass class2 = createClass(barNs, "Class2");
|
||||||
|
Symbol lable2 = createLabel(class2, "Label2", "0x1001104");
|
||||||
|
|
||||||
|
expandClasses();
|
||||||
|
expandNamesapces();
|
||||||
|
|
||||||
|
// verify all leaf nodes
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1::Label1",
|
||||||
|
"FooNs::Class1::BarNs::Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1::Label1",
|
||||||
|
"Class1::BarNs::Class2::Label2",
|
||||||
|
"Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
renameSymbol(lable2, "Label2.renamed");
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1::Label1",
|
||||||
|
"FooNs::Class1::BarNs::Class2::Label2.renamed"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1::Label1",
|
||||||
|
"Class1::BarNs::Class2::Label2.renamed",
|
||||||
|
"Class2::Label2.renamed"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassCategoryDuplicates_NestedClass_ChangeNamespace() throws Exception {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
The Classes folder flattens classes so every class appears at the top level. Because
|
||||||
|
users can expand classes, top level classes may also appear nested under other classes.
|
||||||
|
|
||||||
|
Classes
|
||||||
|
Class1
|
||||||
|
Label1
|
||||||
|
BarNs
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
|
||||||
|
Namespaces
|
||||||
|
FooNs
|
||||||
|
Class1
|
||||||
|
Label1
|
||||||
|
BarNs
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
*/
|
||||||
|
|
||||||
|
Namespace fooNs = createNamespace("FooNs");
|
||||||
|
GhidraClass class1 = createClass(fooNs, "Class1");
|
||||||
|
Namespace barNs = createNamespace(class1, "BarNs");
|
||||||
|
Symbol label1 = createLabel(class1, "Label1", "0x1001100");
|
||||||
|
GhidraClass class2 = createClass(barNs, "Class2");
|
||||||
|
createLabel(class2, "Label2", "0x1001104");
|
||||||
|
|
||||||
|
expandClasses();
|
||||||
|
expandNamesapces();
|
||||||
|
|
||||||
|
// verify all leaf nodes
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1::Label1",
|
||||||
|
"FooNs::Class1::BarNs::Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1::Label1",
|
||||||
|
"Class1::BarNs::Class2::Label2",
|
||||||
|
"Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
moveLabel(label1, barNs);
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1::BarNs::Label1",
|
||||||
|
"FooNs::Class1::BarNs::Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1",
|
||||||
|
"Class1::BarNs::Label1",
|
||||||
|
"Class1::BarNs::Class2::Label2",
|
||||||
|
"Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassCategoryDuplicates_NestedClass_RenameNamespace() throws Exception {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
The Classes folder flattens classes so every class appears at the top level. Because
|
||||||
|
users can expand classes, top level classes may also appear nested under other classes.
|
||||||
|
|
||||||
|
Classes
|
||||||
|
Class1
|
||||||
|
Label1
|
||||||
|
BarNs
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
|
||||||
|
Namespaces
|
||||||
|
FooNs
|
||||||
|
Class1
|
||||||
|
Label1
|
||||||
|
BarNs
|
||||||
|
Class2
|
||||||
|
Label2
|
||||||
|
*/
|
||||||
|
|
||||||
|
Namespace fooNs = createNamespace("FooNs");
|
||||||
|
GhidraClass class1 = createClass(fooNs, "Class1");
|
||||||
|
Namespace barNs = createNamespace(class1, "BarNs");
|
||||||
|
createLabel(class1, "Label1", "0x1001100");
|
||||||
|
GhidraClass class2 = createClass(barNs, "Class2");
|
||||||
|
createLabel(class2, "Label2", "0x1001104");
|
||||||
|
|
||||||
|
expandClasses();
|
||||||
|
expandNamesapces();
|
||||||
|
|
||||||
|
// verify all leaf nodes
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1::Label1",
|
||||||
|
"FooNs::Class1::BarNs::Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1::Label1",
|
||||||
|
"Class1::BarNs::Class2::Label2",
|
||||||
|
"Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
renameNamespace(barNs, "BarNs.renamed");
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertNamespaceNodes(
|
||||||
|
"FooNs::Class1::Label1",
|
||||||
|
"FooNs::Class1::BarNs.renamed::Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
assertClassNodes(
|
||||||
|
"Class1::Label1",
|
||||||
|
"Class1::BarNs.renamed::Class2::Label2",
|
||||||
|
"Class2::Label2"
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionsOnGroup() throws Exception {
|
public void testActionsOnGroup() throws Exception {
|
||||||
// select a group node; only cut, delete, make selection should be
|
// select a group node; only cut, delete, make selection should be
|
||||||
|
@ -403,6 +616,100 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertEquals("MyAnotherLocal", (((SymbolNode) node).getSymbol()).getName());
|
assertEquals("MyAnotherLocal", (((SymbolNode) node).getSymbol()).getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=================================================================================================
|
||||||
|
// Private Methods
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
|
private void expandClasses() {
|
||||||
|
GTreeNode node = rootNode.getChild("Classes");
|
||||||
|
tree.expandTree(node);
|
||||||
|
waitForTree(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expandNamesapces() {
|
||||||
|
GTreeNode node = rootNode.getChild("Namespaces");
|
||||||
|
tree.expandTree(node);
|
||||||
|
waitForTree(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renameSymbol(Symbol s, String newName) {
|
||||||
|
RenameLabelCmd cmd = new RenameLabelCmd(s, newName, SourceType.USER_DEFINED);
|
||||||
|
if (!applyCmd(program, cmd)) {
|
||||||
|
fail("Rename failed: " + cmd.getStatusMsg());
|
||||||
|
}
|
||||||
|
waitForTree(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveLabel(Symbol symbol, Namespace ns) {
|
||||||
|
tx(program, () -> {
|
||||||
|
symbol.setNamespace(ns);
|
||||||
|
});
|
||||||
|
waitForTree(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renameNamespace(Namespace barNs, String newName) {
|
||||||
|
Symbol symbol = barNs.getSymbol();
|
||||||
|
renameSymbol(symbol, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNamespaceNodes(String... paths) {
|
||||||
|
GTreeNode root = tree.getViewRoot();
|
||||||
|
GTreeNode parent = root.getChild("Namespaces");
|
||||||
|
assertNodes(parent, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertClassNodes(String... paths) {
|
||||||
|
GTreeNode root = tree.getViewRoot();
|
||||||
|
GTreeNode parent = root.getChild("Classes");
|
||||||
|
assertNodes(parent, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNodes(GTreeNode category, String... paths) {
|
||||||
|
|
||||||
|
for (String path : paths) {
|
||||||
|
GTreeNode parent = category;
|
||||||
|
String[] parts = path.split("::");
|
||||||
|
for (String name : parts) {
|
||||||
|
GTreeNode child = parent.getChild(name);
|
||||||
|
String message =
|
||||||
|
"Child '%s' not found in parent '%s' \n\tfor path '%s'\n\tCategory '%s'"
|
||||||
|
.formatted(name, parent, path, category);
|
||||||
|
assertNotNull(message, child);
|
||||||
|
parent = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Namespace createNamespace(String name) throws Exception {
|
||||||
|
return createNamespace(program.getGlobalNamespace(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Namespace createNamespace(Namespace parent, String name) throws Exception {
|
||||||
|
CreateNamespacesCmd cmd = new CreateNamespacesCmd(name, parent, SourceType.USER_DEFINED);
|
||||||
|
applyCmd(program, cmd);
|
||||||
|
return cmd.getNamespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GhidraClass createClass(Namespace parent, String name) throws Exception {
|
||||||
|
GhidraClass c = tx(program, () -> {
|
||||||
|
SymbolTable symbolTable = program.getSymbolTable();
|
||||||
|
return symbolTable.createClass(parent, name, SourceType.USER_DEFINED);
|
||||||
|
});
|
||||||
|
assertNotNull(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Symbol createLabel(Namespace parent, String name, String addr) {
|
||||||
|
Symbol s = tx(program, () -> {
|
||||||
|
SymbolTable symbolTable = program.getSymbolTable();
|
||||||
|
AddressFactory af = program.getAddressFactory();
|
||||||
|
Address address = af.getAddress(addr);
|
||||||
|
return symbolTable.createLabel(address, name, parent, SourceType.USER_DEFINED);
|
||||||
|
});
|
||||||
|
assertNotNull(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
private GTreeNode getFunctionsNode() {
|
private GTreeNode getFunctionsNode() {
|
||||||
return runSwing(() -> rootNode.getChild(2));
|
return runSwing(() -> rootNode.getChild(2));
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,26 +232,26 @@ class SymbolTreeTestUtils {
|
||||||
|
|
||||||
selectNode(parenGTreeNode);
|
selectNode(parenGTreeNode);
|
||||||
int childCount = parenGTreeNode.getChildCount();
|
int childCount = parenGTreeNode.getChildCount();
|
||||||
int index = parenGTreeNode.getIndexInParent();
|
int parentIndex = parenGTreeNode.getIndexInParent();
|
||||||
GTreeNode pNode = parenGTreeNode.getParent();
|
GTreeNode grandParentNode = parenGTreeNode.getParent();
|
||||||
|
|
||||||
AbstractDockingTest.performAction(action, getSymbolTreeContext(), false);
|
AbstractDockingTest.performAction(action, getSymbolTreeContext(), false);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
waitForTree();
|
waitForTree();
|
||||||
program.flushEvents();
|
program.flushEvents();
|
||||||
|
|
||||||
if (pNode != null) {
|
if (grandParentNode != null) {
|
||||||
// re-acquire parent
|
parenGTreeNode = grandParentNode.getChild(parentIndex);
|
||||||
parenGTreeNode = pNode.getChild(index);
|
|
||||||
}
|
}
|
||||||
GTreeNode node = parenGTreeNode.getChild(childCount > 0 ? childCount - 1 : 0);
|
|
||||||
|
GTreeNode newNode = parenGTreeNode.getChild(childCount > 0 ? childCount - 1 : 0);
|
||||||
|
|
||||||
waitForTree();
|
waitForTree();
|
||||||
|
|
||||||
runSwing(() -> tree.stopEditing());
|
runSwing(() -> tree.stopEditing());
|
||||||
waitForCondition(() -> !tree.isEditing());
|
waitForCondition(() -> !tree.isEditing());
|
||||||
|
|
||||||
rename(node, newName);
|
rename(newNode, newName);
|
||||||
return parenGTreeNode.getChild(newName);
|
return parenGTreeNode.getChild(newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +274,11 @@ class SymbolTreeTestUtils {
|
||||||
waitForTree();
|
waitForTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void expandTree() {
|
||||||
|
tree.expandAll();
|
||||||
|
waitForTree();
|
||||||
|
}
|
||||||
|
|
||||||
void expandNode(GTreeNode parenGTreeNode) throws Exception {
|
void expandNode(GTreeNode parenGTreeNode) throws Exception {
|
||||||
tree.expandPath(parenGTreeNode);
|
tree.expandPath(parenGTreeNode);
|
||||||
waitForTree();
|
waitForTree();
|
||||||
|
|
|
@ -2144,6 +2144,11 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
TimeUnit.NANOSECONDS));
|
TimeUnit.NANOSECONDS));
|
||||||
*/
|
*/
|
||||||
doWaitForTree(gTree);
|
doWaitForTree(gTree);
|
||||||
|
|
||||||
|
// some client tree operations will launch tasks that wait for the tree and then call a
|
||||||
|
// Swing task to run at some point after that. waitForSwing() is not good enough for these,
|
||||||
|
// since the tree may be using a timer that has not yet expired.
|
||||||
|
waitForExpiringSwingTimers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void doWaitForTree(GTree gTree) {
|
private static void doWaitForTree(GTree gTree) {
|
||||||
|
|
|
@ -597,7 +597,6 @@ public class GTree extends JPanel implements BusyListener {
|
||||||
* @param origin the event type; use {@link EventOrigin#API_GENERATED} if unsure
|
* @param origin the event type; use {@link EventOrigin#API_GENERATED} if unsure
|
||||||
*/
|
*/
|
||||||
public void setSelectionPaths(List<TreePath> paths, boolean expandPaths, EventOrigin origin) {
|
public void setSelectionPaths(List<TreePath> paths, boolean expandPaths, EventOrigin origin) {
|
||||||
|
|
||||||
if (expandPaths) {
|
if (expandPaths) {
|
||||||
expandPaths(paths);
|
expandPaths(paths);
|
||||||
}
|
}
|
||||||
|
@ -1432,7 +1431,7 @@ public class GTree extends JPanel implements BusyListener {
|
||||||
*/
|
*/
|
||||||
public void refilterLater() {
|
public void refilterLater() {
|
||||||
if (isFilteringEnabled && filter != null) {
|
if (isFilteringEnabled && filter != null) {
|
||||||
filterUpdateManager.update();
|
filterUpdateManager.updateLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package generic.timer;
|
package generic.timer;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.*;
|
import java.util.function.*;
|
||||||
|
|
||||||
|
@ -32,6 +32,8 @@ import utility.function.Dummy;
|
||||||
*/
|
*/
|
||||||
public class ExpiringSwingTimer extends GhidraSwingTimer {
|
public class ExpiringSwingTimer extends GhidraSwingTimer {
|
||||||
|
|
||||||
|
private static Set<ExpiringSwingTimer> instances = new HashSet<>();
|
||||||
|
|
||||||
private long startMs = System.currentTimeMillis();
|
private long startMs = System.currentTimeMillis();
|
||||||
private int expireMs;
|
private int expireMs;
|
||||||
private BooleanSupplier isReady;
|
private BooleanSupplier isReady;
|
||||||
|
@ -130,9 +132,16 @@ public class ExpiringSwingTimer extends GhidraSwingTimer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instances.add(this);
|
||||||
super.start();
|
super.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
super.stop();
|
||||||
|
instances.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true the initial expiration period has passed
|
* Returns true the initial expiration period has passed
|
||||||
* @return true if expired
|
* @return true if expired
|
||||||
|
|
|
@ -34,6 +34,7 @@ import javax.swing.tree.*;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
import generic.timer.ExpiringSwingTimer;
|
||||||
import ghidra.framework.ApplicationConfiguration;
|
import ghidra.framework.ApplicationConfiguration;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.datastruct.WeakSet;
|
import ghidra.util.datastruct.WeakSet;
|
||||||
|
@ -1149,6 +1150,39 @@ public class AbstractGuiTest extends AbstractGenericTest {
|
||||||
// Swing Methods
|
// Swing Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
public static boolean waitForExpiringSwingTimers() {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
throw new AssertException("Can't wait for swing from within the swing thread!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this is based on the waitForSwing() timeout; this can be adjusted
|
||||||
|
boolean waited = false;
|
||||||
|
int MAX_SWING_TIMEOUT = 15000;
|
||||||
|
int totalTime = 0;
|
||||||
|
while (totalTime < MAX_SWING_TIMEOUT) {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Set<ExpiringSwingTimer> timers = runSwing(() -> {
|
||||||
|
return (Set<ExpiringSwingTimer>) getInstanceField("instances",
|
||||||
|
ExpiringSwingTimer.class);
|
||||||
|
});
|
||||||
|
if (timers.isEmpty()) {
|
||||||
|
return waited;
|
||||||
|
}
|
||||||
|
|
||||||
|
waited = true;
|
||||||
|
totalTime += sleep(DEFAULT_WAIT_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalTime >= MAX_SWING_TIMEOUT) {
|
||||||
|
Msg.debug(AbstractGenericTest.class,
|
||||||
|
"Timed-out waitinig for ExpiringSwingTimerc after " + totalTime + " ms. ");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for the Swing thread to process any pending events. This method
|
* Waits for the Swing thread to process any pending events. This method
|
||||||
* also waits for any {@link SwingUpdateManager}s that have pending events
|
* also waits for any {@link SwingUpdateManager}s that have pending events
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue