mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-4222 created a builder for DomainObjectListeners to provide a more compact wahy to express the event handling logic
This commit is contained in:
parent
5b53b35968
commit
54214f7f96
16 changed files with 1086 additions and 619 deletions
|
@ -15,15 +15,18 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.machinelearning.functionfinding;
|
package ghidra.machinelearning.functionfinding;
|
||||||
|
|
||||||
|
import static ghidra.framework.model.DomainObjectEvent.*;
|
||||||
|
import static ghidra.program.util.ProgramEvent.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.DomainObjectChangedEvent;
|
||||||
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.program.model.address.AddressSet;
|
import ghidra.program.model.address.AddressSet;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.ProgramEvent;
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.table.*;
|
import ghidra.util.table.*;
|
||||||
|
|
||||||
|
@ -84,30 +87,11 @@ public class FunctionStartTableProvider extends ProgramAssociatedComponentProvid
|
||||||
if (!isVisible()) {
|
if (!isVisible()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ev.contains(DomainObjectEvent.RESTORED)) {
|
if (ev.contains(RESTORED, FUNCTION_ADDED, FUNCTION_REMOVED, CODE_ADDED, CODE_REMOVED,
|
||||||
|
CODE_REPLACED, REFERENCE_TYPE_CHANGED, REFERENCE_ADDED, REFERENCE_REMOVED)) {
|
||||||
model.reload();
|
model.reload();
|
||||||
contextChanged();
|
contextChanged();
|
||||||
}
|
}
|
||||||
for (int i = 0; i < ev.numRecords(); ++i) {
|
|
||||||
DomainObjectChangeRecord doRecord = ev.getChangeRecord(i);
|
|
||||||
EventType eventType = doRecord.getEventType();
|
|
||||||
if (eventType instanceof ProgramEvent type) {
|
|
||||||
switch (type) {
|
|
||||||
case FUNCTION_ADDED:
|
|
||||||
case FUNCTION_REMOVED:
|
|
||||||
case CODE_ADDED:
|
|
||||||
case CODE_REMOVED:
|
|
||||||
case CODE_REPLACED:
|
|
||||||
case REFERENCE_TYPE_CHANGED:
|
|
||||||
case REFERENCE_ADDED:
|
|
||||||
case REFERENCE_REMOVED:
|
|
||||||
model.reload();
|
|
||||||
contextChanged();
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.analysis;
|
package ghidra.app.plugin.core.analysis;
|
||||||
|
|
||||||
|
import static ghidra.framework.model.DomainObjectEvent.*;
|
||||||
|
import static ghidra.program.util.ProgramEvent.*;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
@ -57,7 +60,7 @@ import ghidra.util.task.*;
|
||||||
* Provides support for auto analysis tasks.
|
* Provides support for auto analysis tasks.
|
||||||
* Manages a pipeline or priority of tasks to run given some event has occurred.
|
* Manages a pipeline or priority of tasks to run given some event has occurred.
|
||||||
*/
|
*/
|
||||||
public class AutoAnalysisManager implements DomainObjectListener {
|
public class AutoAnalysisManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the shared thread pool that analyzers can uses to do parallel processing.
|
* The name of the shared thread pool that analyzers can uses to do parallel processing.
|
||||||
|
@ -137,6 +140,7 @@ public class AutoAnalysisManager implements DomainObjectListener {
|
||||||
private List<AutoAnalysisManagerListener> listeners = new CopyOnWriteArrayList<>();
|
private List<AutoAnalysisManagerListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
private EventQueueID eventQueueID;
|
private EventQueueID eventQueueID;
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the plugin giving it the tool that
|
* Creates a new instance of the plugin giving it the tool that
|
||||||
|
@ -144,7 +148,7 @@ public class AutoAnalysisManager implements DomainObjectListener {
|
||||||
*/
|
*/
|
||||||
private AutoAnalysisManager(Program program) {
|
private AutoAnalysisManager(Program program) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
eventQueueID = program.createPrivateEventQueue(this, 500);
|
eventQueueID = program.createPrivateEventQueue(domainObjectListener, 500);
|
||||||
program.addCloseListener(dobj -> dispose());
|
program.addCloseListener(dobj -> dispose());
|
||||||
initializeAnalyzers();
|
initializeAnalyzers();
|
||||||
}
|
}
|
||||||
|
@ -346,95 +350,10 @@ public class AutoAnalysisManager implements DomainObjectListener {
|
||||||
debugOn = b;
|
debugOn = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// private void handleSymbolAddedRenamed(ProgramChangeRecord pcr) {
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
|
||||||
if (program == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (program.isClosed()) {
|
|
||||||
cancelQueuedTasks();
|
|
||||||
dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ignoreChanges) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int eventCnt = ev.numRecords();
|
|
||||||
boolean optionsChanged = false;
|
|
||||||
for (int i = 0; i < eventCnt; ++i) {
|
|
||||||
DomainObjectChangeRecord doRecord = ev.getChangeRecord(i);
|
|
||||||
if (doRecord.getEventType() == ProgramEvent.LANGUAGE_CHANGED) {
|
|
||||||
initializeAnalyzers();
|
|
||||||
}
|
|
||||||
EventType eventType = doRecord.getEventType();
|
|
||||||
if (eventType == DomainObjectEvent.RESTORED ||
|
|
||||||
eventType == DomainObjectEvent.PROPERTY_CHANGED) {
|
|
||||||
if (!optionsChanged) {
|
|
||||||
initializeOptions();
|
|
||||||
Preferences.store();
|
|
||||||
optionsChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (eventType instanceof ProgramEvent pe) {
|
|
||||||
ProgramChangeRecord pcr = (ProgramChangeRecord) doRecord;
|
|
||||||
switch (pe) {
|
|
||||||
case FUNCTION_CHANGED:
|
|
||||||
FunctionChangeRecord fcr = (FunctionChangeRecord) doRecord;
|
|
||||||
Address entry = fcr.getFunction().getEntryPoint();
|
|
||||||
if (fcr.isFunctionSignatureChange()) {
|
|
||||||
functionSignatureChanged(entry);
|
|
||||||
}
|
|
||||||
else if (fcr.isFunctionModifierChange()) {
|
|
||||||
functionModifierChanged(entry);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FUNCTION_ADDED:
|
|
||||||
case FUNCTION_BODY_CHANGED:
|
|
||||||
Function func = (Function) pcr.getObject();
|
|
||||||
if (!func.isExternal()) {
|
|
||||||
functionDefined(func.getEntryPoint());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FUNCTION_REMOVED:
|
|
||||||
Address oldEntry = pcr.getStart();
|
|
||||||
functionTasks.notifyRemoved(oldEntry);
|
|
||||||
break;
|
|
||||||
case FALLTHROUGH_CHANGED:
|
|
||||||
case FLOW_OVERRIDE_CHANGED:
|
|
||||||
case LENGTH_OVERRIDE_CHANGED:
|
|
||||||
// TODO: not sure if this should be done this way or explicitly
|
|
||||||
// via the application commands (this is inconsistent with other
|
|
||||||
// codeDefined cases which do not rely on change events (e.g., disassembly)
|
|
||||||
codeDefined(new AddressSet(pcr.getStart()));
|
|
||||||
break;
|
|
||||||
// FIXME: must resolve cyclic issues before this can be done
|
|
||||||
// case MEM_REFERENCE_ADDED:
|
|
||||||
// // Allow high-priority reference-driven code analyzers a
|
|
||||||
// // shot at processing computed flows determined during
|
|
||||||
// // constant propagation.
|
|
||||||
// pcr = (ProgramChangeRecord) doRecord;
|
|
||||||
// Reference ref = (Reference) pcr.getNewValue();
|
|
||||||
// RefType refType = ref.getReferenceType();
|
|
||||||
// if (refType.isComputed()) {
|
|
||||||
// codeDefined(ref.getFromAddress());
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
case CODE_ADDED:
|
|
||||||
if (pcr.getNewValue() instanceof Data) {
|
|
||||||
AddressSet addressSet = new AddressSet(pcr.getStart(), pcr.getEnd());
|
|
||||||
dataDefined(addressSet);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// TODO: Add Symbol analyzer type
|
|
||||||
// case SYMBOL_ADDED:
|
|
||||||
// case SYMBOL_RENAMED:
|
|
||||||
// pcr = (ProgramChangeRecord) doRecord;
|
|
||||||
// // if a function is created using the current name, don't throw symbol added/renamed
|
// // if a function is created using the current name, don't throw symbol added/renamed
|
||||||
// // split variable changed/added from SYMBOL added - change record is already different
|
// // split variable changed/added from SYMBOL added - change record is already different
|
||||||
// if (pcr.getObject() != null &&
|
// if (pcr.getObject() != null && pcr.getObject() instanceof VariableSymbolDB) {
|
||||||
// pcr.getObject() instanceof VariableSymbolDB) {
|
|
||||||
// break;
|
// break;
|
||||||
// }
|
// }
|
||||||
// Symbol sym = null;
|
// Symbol sym = null;
|
||||||
|
@ -453,11 +372,81 @@ public class AutoAnalysisManager implements DomainObjectListener {
|
||||||
// sym.getSource() != SourceType.DEFAULT) {
|
// sym.getSource() != SourceType.DEFAULT) {
|
||||||
// symbolTasks.notifyAdded(sym.getAddress());
|
// symbolTasks.notifyAdded(sym.getAddress());
|
||||||
// }
|
// }
|
||||||
// break;
|
//
|
||||||
default:
|
// }
|
||||||
|
|
||||||
|
private void handleCodeAdded(ProgramChangeRecord rec) {
|
||||||
|
if (rec.getNewValue() instanceof Data) {
|
||||||
|
AddressSet addressSet = new AddressSet(rec.getStart(), rec.getEnd());
|
||||||
|
dataDefined(addressSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleOverrides(ProgramChangeRecord rec) {
|
||||||
|
// TODO: not sure if this should be done this way or explicitly
|
||||||
|
// via the application commands (this is inconsistent with other
|
||||||
|
// codeDefined cases which do not rely on change events (e.g., disassembly)
|
||||||
|
codeDefined(new AddressSet(rec.getStart()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleFunctionAddedOrBodyChanged(ProgramChangeRecord rec) {
|
||||||
|
Function func = (Function) rec.getObject();
|
||||||
|
if (!func.isExternal()) {
|
||||||
|
functionDefined(func.getEntryPoint());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFunctionChanged(FunctionChangeRecord rec) {
|
||||||
|
Address entry = rec.getFunction().getEntryPoint();
|
||||||
|
if (rec.isFunctionSignatureChange()) {
|
||||||
|
functionSignatureChanged(entry);
|
||||||
|
}
|
||||||
|
else if (rec.isFunctionModifierChange()) {
|
||||||
|
functionModifierChanged(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetOptions() {
|
||||||
|
initializeOptions();
|
||||||
|
Preferences.store();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
|
//@formatter:off
|
||||||
|
return new DomainObjectListenerBuilder(this)
|
||||||
|
.ignoreWhen(this::shouldIgnoreEvent)
|
||||||
|
.any(LANGUAGE_CHANGED)
|
||||||
|
.call(this::initializeAnalyzers)
|
||||||
|
.any(RESTORED, PROPERTY_CHANGED)
|
||||||
|
.call(this::resetOptions)
|
||||||
|
.with(FunctionChangeRecord.class)
|
||||||
|
.each(FUNCTION_CHANGED)
|
||||||
|
.call(r -> handleFunctionChanged(r))
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(FUNCTION_ADDED, FUNCTION_BODY_CHANGED)
|
||||||
|
.call(r -> handleFunctionAddedOrBodyChanged(r))
|
||||||
|
.each(FUNCTION_REMOVED)
|
||||||
|
.call( r -> functionTasks.notifyRemoved(r.getStart()))
|
||||||
|
.each(FALLTHROUGH_CHANGED, FLOW_OVERRIDE_CHANGED, LENGTH_OVERRIDE_CHANGED)
|
||||||
|
.call(r -> handleOverrides(r))
|
||||||
|
.each(CODE_ADDED)
|
||||||
|
.call(r -> handleCodeAdded(r))
|
||||||
|
// .each(SYMBOL_ADDED, SYMBOL_RENAMED) // TODO add symbol analyzer type
|
||||||
|
// .call(r -> handleSymbolAddedRenamed(r)
|
||||||
|
.build();
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldIgnoreEvent() {
|
||||||
|
if (program == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (program.isClosed()) {
|
||||||
|
cancelQueuedTasks();
|
||||||
|
dispose();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return ignoreChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -978,8 +967,6 @@ public class AutoAnalysisManager implements DomainObjectListener {
|
||||||
return; // already been disposed()
|
return; // already been disposed()
|
||||||
}
|
}
|
||||||
|
|
||||||
localProgram.removeListener(this);
|
|
||||||
|
|
||||||
synchronized (this) { // sync against multiple dispose calls
|
synchronized (this) { // sync against multiple dispose calls
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
service.dispose();
|
service.dispose();
|
||||||
|
@ -1815,4 +1802,5 @@ public class AutoAnalysisManager implements DomainObjectListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,8 @@ import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.ProgramPlugin;
|
import ghidra.app.plugin.ProgramPlugin;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.framework.cmd.CompoundCmd;
|
import ghidra.framework.cmd.CompoundCmd;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.PluginInfo;
|
import ghidra.framework.plugintool.PluginInfo;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -67,8 +68,7 @@ import resources.MultiIconBuilder;
|
||||||
eventsProduced = { ProgramSelectionPluginEvent.class }
|
eventsProduced = { ProgramSelectionPluginEvent.class }
|
||||||
)
|
)
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public class BookmarkPlugin extends ProgramPlugin
|
public class BookmarkPlugin extends ProgramPlugin implements PopupActionProvider, BookmarkService {
|
||||||
implements DomainObjectListener, PopupActionProvider, BookmarkService {
|
|
||||||
|
|
||||||
private final static int MAX_DELETE_ACTIONS = 10;
|
private final static int MAX_DELETE_ACTIONS = 10;
|
||||||
|
|
||||||
|
@ -88,6 +88,8 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
private Map<String, BookmarkNavigator> bookmarkNavigators = new HashMap<>(); // maps type names to BookmarkNavigators
|
private Map<String, BookmarkNavigator> bookmarkNavigators = new HashMap<>(); // maps type names to BookmarkNavigators
|
||||||
private NavUpdater navUpdater;
|
private NavUpdater navUpdater;
|
||||||
|
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
public BookmarkPlugin(PluginTool tool) {
|
public BookmarkPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
|
||||||
|
@ -97,6 +99,21 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
createActions();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
|
// @formatter:off
|
||||||
|
DomainObjectListenerBuilder builder = new DomainObjectListenerBuilder(this);
|
||||||
|
return builder
|
||||||
|
.any(RESTORED, MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED).terminate(this::reload)
|
||||||
|
.any(BOOKMARK_TYPE_REMOVED).call(() ->repaintMgr.update())
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(BOOKMARK_REMOVED).call(this::bookmarkRemoved)
|
||||||
|
.each(BOOKMARK_ADDED).call(this::bookmarkAdded)
|
||||||
|
.each(BOOKMARK_CHANGED).call(this::bookmarkChanged)
|
||||||
|
.each(BOOKMARK_TYPE_ADDED).call(this::typeAdded)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readConfigState(SaveState saveState) {
|
public void readConfigState(SaveState saveState) {
|
||||||
super.readConfigState(saveState);
|
super.readConfigState(saveState);
|
||||||
|
@ -214,7 +231,7 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
markerService = null;
|
markerService = null;
|
||||||
|
|
||||||
if (currentProgram != null) {
|
if (currentProgram != null) {
|
||||||
currentProgram.removeListener(this);
|
currentProgram.removeListener(domainObjectListener);
|
||||||
}
|
}
|
||||||
currentProgram = null;
|
currentProgram = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
@ -296,48 +313,9 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
navUpdater.addType(type);
|
navUpdater.addType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void reload() {
|
||||||
public synchronized void domainObjectChanged(DomainObjectChangedEvent event) {
|
|
||||||
|
|
||||||
if (event.contains(RESTORED, MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED)) {
|
|
||||||
scheduleUpdate(null);
|
scheduleUpdate(null);
|
||||||
provider.reload();
|
provider.reload();
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!event.contains(BOOKMARK_REMOVED, BOOKMARK_ADDED, BOOKMARK_CHANGED, BOOKMARK_TYPE_ADDED,
|
|
||||||
BOOKMARK_TYPE_REMOVED)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < event.numRecords(); i++) {
|
|
||||||
DomainObjectChangeRecord record = event.getChangeRecord(i);
|
|
||||||
|
|
||||||
EventType eventType = record.getEventType();
|
|
||||||
if (!(record instanceof ProgramChangeRecord rec)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (eventType instanceof ProgramEvent ev) {
|
|
||||||
switch (ev) {
|
|
||||||
case BOOKMARK_REMOVED:
|
|
||||||
bookmarkRemoved((Bookmark) rec.getObject());
|
|
||||||
break;
|
|
||||||
case BOOKMARK_ADDED:
|
|
||||||
bookmarkAdded((Bookmark) rec.getObject());
|
|
||||||
break;
|
|
||||||
case BOOKMARK_CHANGED:
|
|
||||||
bookmarkChanged((Bookmark) rec.getObject());
|
|
||||||
break;
|
|
||||||
case BOOKMARK_TYPE_ADDED:
|
|
||||||
BookmarkType bookmarkType = (BookmarkType) rec.getObject();
|
|
||||||
if (bookmarkType != null) {
|
|
||||||
typeAdded(bookmarkType.getTypeString());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
repaintMgr.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -345,12 +323,18 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
tool.showComponentProvider(provider, visible);
|
tool.showComponentProvider(provider, visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void typeAdded(String type) {
|
private void typeAdded(ProgramChangeRecord rec) {
|
||||||
provider.typeAdded(type);
|
BookmarkType type = (BookmarkType) rec.getObject();
|
||||||
getBookmarkNavigator(bookmarkMgr.getBookmarkType(type));
|
if (type == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
provider.typeAdded(type.getTypeString());
|
||||||
|
getBookmarkNavigator(bookmarkMgr.getBookmarkType(type.getTypeString()));
|
||||||
|
repaintMgr.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bookmarkChanged(Bookmark bookmark) {
|
private void bookmarkChanged(ProgramChangeRecord rec) {
|
||||||
|
Bookmark bookmark = (Bookmark) rec.getObject();
|
||||||
if (bookmark == null) {
|
if (bookmark == null) {
|
||||||
scheduleUpdate(null);
|
scheduleUpdate(null);
|
||||||
provider.reload();
|
provider.reload();
|
||||||
|
@ -360,9 +344,11 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
nav.add(bookmark.getAddress());
|
nav.add(bookmark.getAddress());
|
||||||
scheduleUpdate(bookmark.getType().getTypeString());
|
scheduleUpdate(bookmark.getType().getTypeString());
|
||||||
provider.bookmarkChanged(bookmark);
|
provider.bookmarkChanged(bookmark);
|
||||||
|
repaintMgr.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bookmarkAdded(Bookmark bookmark) {
|
private void bookmarkAdded(ProgramChangeRecord rec) {
|
||||||
|
Bookmark bookmark = (Bookmark) rec.getObject();
|
||||||
if (bookmark == null) {
|
if (bookmark == null) {
|
||||||
scheduleUpdate(null);
|
scheduleUpdate(null);
|
||||||
provider.reload();
|
provider.reload();
|
||||||
|
@ -372,9 +358,11 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
nav.add(bookmark.getAddress());
|
nav.add(bookmark.getAddress());
|
||||||
// scheduleUpdate(bookmark.getType().getTypeString());
|
// scheduleUpdate(bookmark.getType().getTypeString());
|
||||||
provider.bookmarkAdded(bookmark);
|
provider.bookmarkAdded(bookmark);
|
||||||
|
repaintMgr.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bookmarkRemoved(Bookmark bookmark) {
|
private void bookmarkRemoved(ProgramChangeRecord rec) {
|
||||||
|
Bookmark bookmark = (Bookmark) rec.getObject();
|
||||||
if (bookmark == null) {
|
if (bookmark == null) {
|
||||||
scheduleUpdate(null);
|
scheduleUpdate(null);
|
||||||
provider.reload();
|
provider.reload();
|
||||||
|
@ -390,13 +378,14 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
provider.bookmarkRemoved(bookmark);
|
provider.bookmarkRemoved(bookmark);
|
||||||
|
repaintMgr.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void programDeactivated(Program program) {
|
protected synchronized void programDeactivated(Program program) {
|
||||||
provider.setProgram(null);
|
provider.setProgram(null);
|
||||||
navUpdater.setProgram(null);
|
navUpdater.setProgram(null);
|
||||||
program.removeListener(this);
|
program.removeListener(domainObjectListener);
|
||||||
disposeAllBookmarkers();
|
disposeAllBookmarkers();
|
||||||
bookmarkMgr = null;
|
bookmarkMgr = null;
|
||||||
}
|
}
|
||||||
|
@ -413,7 +402,7 @@ public class BookmarkPlugin extends ProgramPlugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void programActivated(Program program) {
|
protected synchronized void programActivated(Program program) {
|
||||||
program.addListener(this);
|
program.addListener(domainObjectListener);
|
||||||
navUpdater.setProgram(program);
|
navUpdater.setProgram(program);
|
||||||
initializeBookmarkers();
|
initializeBookmarkers();
|
||||||
provider.setProgram(program);
|
provider.setProgram(program);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.app.plugin.core.calltree;
|
package ghidra.app.plugin.core.calltree;
|
||||||
|
|
||||||
import static ghidra.framework.model.DomainObjectEvent.*;
|
import static ghidra.framework.model.DomainObjectEvent.*;
|
||||||
|
import static ghidra.program.util.ProgramEvent.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
@ -38,7 +39,8 @@ import generic.theme.GIcon;
|
||||||
import ghidra.app.events.ProgramLocationPluginEvent;
|
import ghidra.app.events.ProgramLocationPluginEvent;
|
||||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||||
import ghidra.app.services.GoToService;
|
import ghidra.app.services.GoToService;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.preferences.Preferences;
|
import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.program.database.symbol.FunctionSymbol;
|
import ghidra.program.database.symbol.FunctionSymbol;
|
||||||
|
@ -54,7 +56,7 @@ import ghidra.util.task.SwingUpdateManager;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import resources.Icons;
|
import resources.Icons;
|
||||||
|
|
||||||
public class CallTreeProvider extends ComponentProviderAdapter implements DomainObjectListener {
|
public class CallTreeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
static final String EXPAND_ACTION_NAME = "Fully Expand Selected Nodes";
|
static final String EXPAND_ACTION_NAME = "Fully Expand Selected Nodes";
|
||||||
static final String TITLE = "Function Call Trees";
|
static final String TITLE = "Function Call Trees";
|
||||||
|
@ -94,6 +96,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
*/
|
*/
|
||||||
private AtomicInteger recurseDepth = new AtomicInteger();
|
private AtomicInteger recurseDepth = new AtomicInteger();
|
||||||
private NumberIcon recurseIcon;
|
private NumberIcon recurseIcon;
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
public CallTreeProvider(CallTreePlugin plugin, boolean isPrimary) {
|
public CallTreeProvider(CallTreePlugin plugin, boolean isPrimary) {
|
||||||
super(plugin.getTool(), TITLE, plugin.getName());
|
super(plugin.getTool(), TITLE, plugin.getName());
|
||||||
|
@ -852,7 +855,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
incomingTree.dispose();
|
incomingTree.dispose();
|
||||||
outgoingTree.dispose();
|
outgoingTree.dispose();
|
||||||
if (currentProgram != null) {
|
if (currentProgram != null) {
|
||||||
currentProgram.removeListener(this);
|
currentProgram.removeListener(domainObjectListener);
|
||||||
currentProgram = null;
|
currentProgram = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -891,7 +894,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
// changes, which means we will get here while setting the location, but our program
|
// changes, which means we will get here while setting the location, but our program
|
||||||
// will have been null'ed out.
|
// will have been null'ed out.
|
||||||
currentProgram = plugin.getCurrentProgram();
|
currentProgram = plugin.getCurrentProgram();
|
||||||
currentProgram.addListener(this);
|
currentProgram.addListener(domainObjectListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
Function function = plugin.getFunction(location);
|
Function function = plugin.getFunction(location);
|
||||||
|
@ -991,7 +994,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
currentProgram = program;
|
currentProgram = program;
|
||||||
currentProgram.addListener(this);
|
currentProgram.addListener(domainObjectListener);
|
||||||
doSetLocation(location);
|
doSetLocation(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1001,7 +1004,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
currentProgram = program;
|
currentProgram = program;
|
||||||
currentProgram.addListener(this);
|
currentProgram.addListener(domainObjectListener);
|
||||||
setLocation(plugin.getCurrentLocation());
|
setLocation(plugin.getCurrentLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1018,7 +1021,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
return; // not my program
|
return; // not my program
|
||||||
}
|
}
|
||||||
|
|
||||||
program.removeListener(this);
|
program.removeListener(domainObjectListener);
|
||||||
clearState();
|
clearState();
|
||||||
|
|
||||||
currentProgram = null;
|
currentProgram = null;
|
||||||
|
@ -1044,39 +1047,24 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
return navigateIncomingToggleAction.isSelected();
|
return navigateIncomingToggleAction.isSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent event) {
|
// @formatter:off
|
||||||
if (!isVisible()) {
|
return new DomainObjectListenerBuilder(this)
|
||||||
return;
|
.ignoreWhen(() -> !isVisible() || isEmpty())
|
||||||
|
.any(RESTORED).terminate(() -> setStale(true))
|
||||||
|
.any(MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED, SYMBOL_ADDED, SYMBOL_REMOVED,
|
||||||
|
REFERENCE_ADDED, REFERENCE_REMOVED)
|
||||||
|
.call(() -> setStale(true))
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(SYMBOL_RENAMED).call(r -> handleSymbolRenamed(r))
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmpty()) {
|
private void handleSymbolRenamed(ProgramChangeRecord r) {
|
||||||
return; // nothing to update
|
Symbol symbol = (Symbol) r.getObject();
|
||||||
}
|
|
||||||
|
|
||||||
if (event.contains(RESTORED)) {
|
|
||||||
setStale(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < event.numRecords(); i++) {
|
|
||||||
DomainObjectChangeRecord domainObjectRecord = event.getChangeRecord(i);
|
|
||||||
EventType eventType = domainObjectRecord.getEventType();
|
|
||||||
if (eventType instanceof ProgramEvent type) {
|
|
||||||
switch (type) {
|
|
||||||
case MEMORY_BLOCK_MOVED:
|
|
||||||
case MEMORY_BLOCK_REMOVED:
|
|
||||||
case SYMBOL_ADDED:
|
|
||||||
case SYMBOL_REMOVED:
|
|
||||||
case REFERENCE_ADDED:
|
|
||||||
case REFERENCE_REMOVED:
|
|
||||||
setStale(true);
|
|
||||||
break;
|
|
||||||
case SYMBOL_RENAMED:
|
|
||||||
Symbol symbol =
|
|
||||||
(Symbol) ((ProgramChangeRecord) domainObjectRecord).getObject();
|
|
||||||
if (!(symbol instanceof FunctionSymbol)) {
|
if (!(symbol instanceof FunctionSymbol)) {
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionSymbol functionSymbol = (FunctionSymbol) symbol;
|
FunctionSymbol functionSymbol = (FunctionSymbol) symbol;
|
||||||
|
@ -1087,11 +1075,6 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
|
||||||
|
|
||||||
incomingTree.runTask(new UpdateFunctionNodeTask(incomingTree, function));
|
incomingTree.runTask(new UpdateFunctionNodeTask(incomingTree, function));
|
||||||
outgoingTree.runTask(new UpdateFunctionNodeTask(outgoingTree, function));
|
outgoingTree.runTask(new UpdateFunctionNodeTask(outgoingTree, function));
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEmpty() {
|
private boolean isEmpty() {
|
||||||
|
|
|
@ -24,8 +24,8 @@ import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.ProgramPlugin;
|
import ghidra.app.plugin.ProgramPlugin;
|
||||||
import ghidra.app.services.GoToService;
|
import ghidra.app.services.GoToService;
|
||||||
import ghidra.framework.model.DomainObjectChangedEvent;
|
|
||||||
import ghidra.framework.model.DomainObjectListener;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.plugintool.PluginInfo;
|
import ghidra.framework.plugintool.PluginInfo;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
@ -52,11 +52,12 @@ import ghidra.util.task.SwingUpdateManager;
|
||||||
servicesRequired = { GoToService.class }
|
servicesRequired = { GoToService.class }
|
||||||
)
|
)
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectListener {
|
public class CommentWindowPlugin extends ProgramPlugin {
|
||||||
|
|
||||||
private DockingAction selectAction;
|
private DockingAction selectAction;
|
||||||
private CommentWindowProvider provider;
|
private CommentWindowProvider provider;
|
||||||
private SwingUpdateManager reloadUpdateMgr;
|
private SwingUpdateManager reloadUpdateMgr;
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
public CommentWindowPlugin(PluginTool tool) {
|
public CommentWindowPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
@ -76,24 +77,23 @@ public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectLi
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
reloadUpdateMgr.dispose();
|
reloadUpdateMgr.dispose();
|
||||||
if (currentProgram != null) {
|
if (currentProgram != null) {
|
||||||
currentProgram.removeListener(this);
|
currentProgram.removeListener(domainObjectListener);
|
||||||
}
|
}
|
||||||
provider.dispose();
|
provider.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
// @formatter:off
|
||||||
|
return new DomainObjectListenerBuilder(this)
|
||||||
// reload the table if an undo/redo or clear code with options event happens (it isn't the
|
.any(RESTORED, CODE_REMOVED).terminate(this::reload)
|
||||||
// same as a delete comment)
|
.with(CommentChangeRecord.class)
|
||||||
if (ev.contains(RESTORED, CODE_REMOVED)) {
|
.each(COMMENT_CHANGED).call(this::handleCommentChanged)
|
||||||
reload();
|
.build();
|
||||||
return;
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.forEach(COMMENT_CHANGED, r -> {
|
private void handleCommentChanged(CommentChangeRecord ccr) {
|
||||||
CommentChangeRecord ccr = (CommentChangeRecord) r;
|
|
||||||
int commentType = ccr.getCommentType();
|
int commentType = ccr.getCommentType();
|
||||||
String oldComment = ccr.getOldComment();
|
String oldComment = ccr.getOldComment();
|
||||||
String newComment = ccr.getNewComment();
|
String newComment = ccr.getNewComment();
|
||||||
|
@ -113,7 +113,6 @@ public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectLi
|
||||||
provider.getComponent().repaint();
|
provider.getComponent().repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reload() {
|
private void reload() {
|
||||||
|
@ -126,13 +125,13 @@ public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectLi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void programActivated(Program program) {
|
protected void programActivated(Program program) {
|
||||||
program.addListener(this);
|
program.addListener(domainObjectListener);
|
||||||
provider.programOpened(program);
|
provider.programOpened(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void programDeactivated(Program program) {
|
protected void programDeactivated(Program program) {
|
||||||
program.removeListener(this);
|
program.removeListener(domainObjectListener);
|
||||||
provider.programClosed();
|
provider.programClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.ProgramPlugin;
|
import ghidra.app.plugin.ProgramPlugin;
|
||||||
import ghidra.app.services.GoToService;
|
import ghidra.app.services.GoToService;
|
||||||
import ghidra.app.services.ProgramTreeService;
|
import ghidra.app.services.ProgramTreeService;
|
||||||
import ghidra.framework.model.DomainObjectChangedEvent;
|
|
||||||
import ghidra.framework.model.DomainObjectListener;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
@ -59,7 +59,7 @@ import ghidra.util.task.SwingUpdateManager;
|
||||||
eventsConsumed = { ViewChangedPluginEvent.class }
|
eventsConsumed = { ViewChangedPluginEvent.class }
|
||||||
)
|
)
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListener {
|
public class DataWindowPlugin extends ProgramPlugin {
|
||||||
|
|
||||||
private DockingAction selectAction;
|
private DockingAction selectAction;
|
||||||
private FilterAction filterAction;
|
private FilterAction filterAction;
|
||||||
|
@ -68,6 +68,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe
|
||||||
private SwingUpdateManager resetUpdateMgr;
|
private SwingUpdateManager resetUpdateMgr;
|
||||||
private SwingUpdateManager reloadUpdateMgr;
|
private SwingUpdateManager reloadUpdateMgr;
|
||||||
private boolean resetTypesNeeded;
|
private boolean resetTypesNeeded;
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
public DataWindowPlugin(PluginTool tool) {
|
public DataWindowPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
@ -90,35 +91,32 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe
|
||||||
reloadUpdateMgr.dispose();
|
reloadUpdateMgr.dispose();
|
||||||
resetUpdateMgr.dispose();
|
resetUpdateMgr.dispose();
|
||||||
if (currentProgram != null) {
|
if (currentProgram != null) {
|
||||||
currentProgram.removeListener(this);
|
currentProgram.removeListener(domainObjectListener);
|
||||||
}
|
}
|
||||||
provider.dispose();
|
provider.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
DomainObjectListener createDomainObjectListener() {
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
// @formatter:off
|
||||||
if (ev.contains(RESTORED)) {
|
return new DomainObjectListenerBuilder(this)
|
||||||
resetTypes();
|
.any(RESTORED)
|
||||||
reload();
|
.terminate(() -> resetTypes())
|
||||||
return;
|
.any(MEMORY_BLOCK_ADDED, MEMORY_BLOCK_REMOVED, CODE_REMOVED)
|
||||||
|
.terminate(e -> reload())
|
||||||
|
.any(DATA_TYPE_ADDED,DATA_TYPE_CHANGED, DATA_TYPE_MOVED, DATA_TYPE_RENAMED,
|
||||||
|
DATA_TYPE_REPLACED, DATA_TYPE_SETTING_CHANGED)
|
||||||
|
.terminate(() -> resetTypes())
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(CODE_ADDED).call(r -> codeAdded(r))
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.contains(DATA_TYPE_ADDED, DATA_TYPE_CHANGED, DATA_TYPE_MOVED, DATA_TYPE_RENAMED,
|
private void codeAdded(ProgramChangeRecord rec) {
|
||||||
DATA_TYPE_REPLACED, DATA_TYPE_SETTING_CHANGED)) {
|
|
||||||
resetTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.contains(MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED, CODE_REMOVED)) {
|
|
||||||
reload();
|
|
||||||
return; // if we are going to reload, no need to check for data additions.
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.forEach(CODE_ADDED, rec -> {
|
|
||||||
if (rec.getNewValue() instanceof Data) {
|
if (rec.getNewValue() instanceof Data) {
|
||||||
provider.dataAdded(((ProgramChangeRecord) rec).getStart());
|
provider.dataAdded(rec.getStart());
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void reload() {
|
void reload() {
|
||||||
|
@ -143,7 +141,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void programActivated(Program program) {
|
protected void programActivated(Program program) {
|
||||||
program.addListener(this);
|
program.addListener(domainObjectListener);
|
||||||
provider.programOpened(program);
|
provider.programOpened(program);
|
||||||
filterAction.programOpened(program);
|
filterAction.programOpened(program);
|
||||||
resetTypes();
|
resetTypes();
|
||||||
|
@ -151,7 +149,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void programDeactivated(Program program) {
|
protected void programDeactivated(Program program) {
|
||||||
program.removeListener(this);
|
program.removeListener(domainObjectListener);
|
||||||
provider.programClosed();
|
provider.programClosed();
|
||||||
filterAction.programClosed();
|
filterAction.programClosed();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@ import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.ProgramPlugin;
|
import ghidra.app.plugin.ProgramPlugin;
|
||||||
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction;
|
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction;
|
||||||
import ghidra.app.services.FunctionComparisonService;
|
import ghidra.app.services.FunctionComparisonService;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.plugintool.PluginInfo;
|
import ghidra.framework.plugintool.PluginInfo;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
@ -34,7 +35,6 @@ import ghidra.program.model.listing.Function;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.model.symbol.Symbol;
|
import ghidra.program.model.symbol.Symbol;
|
||||||
import ghidra.program.util.ProgramChangeRecord;
|
import ghidra.program.util.ProgramChangeRecord;
|
||||||
import ghidra.program.util.ProgramEvent;
|
|
||||||
import ghidra.util.table.SelectionNavigationAction;
|
import ghidra.util.table.SelectionNavigationAction;
|
||||||
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
||||||
import ghidra.util.task.SwingUpdateManager;
|
import ghidra.util.task.SwingUpdateManager;
|
||||||
|
@ -49,12 +49,13 @@ import ghidra.util.task.SwingUpdateManager;
|
||||||
eventsConsumed = { ProgramClosedPluginEvent.class }
|
eventsConsumed = { ProgramClosedPluginEvent.class }
|
||||||
)
|
)
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener {
|
public class FunctionWindowPlugin extends ProgramPlugin {
|
||||||
|
|
||||||
private DockingAction selectAction;
|
private DockingAction selectAction;
|
||||||
private DockingAction compareFunctionsAction;
|
private DockingAction compareFunctionsAction;
|
||||||
private FunctionWindowProvider provider;
|
private FunctionWindowProvider provider;
|
||||||
private SwingUpdateManager swingMgr;
|
private SwingUpdateManager swingMgr;
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
public FunctionWindowPlugin(PluginTool tool) {
|
public FunctionWindowPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
@ -80,7 +81,7 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if (currentProgram != null) {
|
if (currentProgram != null) {
|
||||||
currentProgram.removeListener(this);
|
currentProgram.removeListener(domainObjectListener);
|
||||||
}
|
}
|
||||||
swingMgr.dispose();
|
swingMgr.dispose();
|
||||||
if (provider != null) {
|
if (provider != null) {
|
||||||
|
@ -106,88 +107,66 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
// @formatter:off
|
||||||
|
return new DomainObjectListenerBuilder(this)
|
||||||
if (!provider.isVisible()) {
|
.ignoreWhen(() -> !provider.isVisible())
|
||||||
return;
|
.any(RESTORED, MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED).terminate(provider::reload)
|
||||||
|
.any(CODE_ADDED, CODE_REMOVED).call(swingMgr::update)
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(FUNCTION_ADDED).call(this::functionAdded)
|
||||||
|
.each(FUNCTION_REMOVED).call(this::functionRemoved)
|
||||||
|
.each(FUNCTION_CHANGED).call(this::functionChanged)
|
||||||
|
.each(SYMBOL_ADDED, SYMBOL_PRIMARY_STATE_CHANGED).call(this::symbolChanged)
|
||||||
|
.each(SYMBOL_RENAMED).call(this::symbolRenamed)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.contains(RESTORED, MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED)) {
|
private void functionAdded(ProgramChangeRecord rec) {
|
||||||
provider.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < ev.numRecords(); ++i) {
|
|
||||||
DomainObjectChangeRecord doRecord = ev.getChangeRecord(i);
|
|
||||||
|
|
||||||
EventType eventType = doRecord.getEventType();
|
|
||||||
|
|
||||||
if (eventType instanceof ProgramEvent type) {
|
|
||||||
switch (type) {
|
|
||||||
case CODE_ADDED:
|
|
||||||
case CODE_REMOVED:
|
|
||||||
swingMgr.update();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FUNCTION_ADDED:
|
|
||||||
ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i);
|
|
||||||
Function function = (Function) rec.getObject();
|
Function function = (Function) rec.getObject();
|
||||||
provider.functionAdded(function);
|
provider.functionAdded(function);
|
||||||
break;
|
}
|
||||||
case FUNCTION_REMOVED:
|
|
||||||
rec = (ProgramChangeRecord) ev.getChangeRecord(i);
|
private void functionRemoved(ProgramChangeRecord rec) {
|
||||||
function = (Function) rec.getObject();
|
Function function = (Function) rec.getObject();
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
provider.functionRemoved(function);
|
provider.functionRemoved(function);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case FUNCTION_CHANGED:
|
|
||||||
rec = (ProgramChangeRecord) ev.getChangeRecord(i);
|
private void functionChanged(ProgramChangeRecord rec) {
|
||||||
function = (Function) rec.getObject();
|
Function function = (Function) rec.getObject();
|
||||||
provider.update(function);
|
provider.update(function);
|
||||||
break;
|
}
|
||||||
case SYMBOL_ADDED:
|
|
||||||
case SYMBOL_PRIMARY_STATE_CHANGED:
|
private void symbolChanged(ProgramChangeRecord rec) {
|
||||||
rec = (ProgramChangeRecord) ev.getChangeRecord(i);
|
|
||||||
Symbol sym = (Symbol) rec.getNewValue();
|
Symbol sym = (Symbol) rec.getNewValue();
|
||||||
Address addr = sym.getAddress();
|
Address addr = sym.getAddress();
|
||||||
function = currentProgram.getListing().getFunctionAt(addr);
|
Function function = currentProgram.getListing().getFunctionAt(addr);
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
provider.update(function);
|
provider.update(function);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case SYMBOL_RENAMED:
|
|
||||||
rec = (ProgramChangeRecord) ev.getChangeRecord(i);
|
private void symbolRenamed(ProgramChangeRecord rec) {
|
||||||
sym = (Symbol) rec.getObject();
|
Symbol sym = (Symbol) rec.getObject();
|
||||||
addr = sym.getAddress();
|
Address addr = sym.getAddress();
|
||||||
function = currentProgram.getListing().getFunctionAt(addr);
|
Function function = currentProgram.getListing().getFunctionAt(addr);
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
provider.update(function);
|
provider.update(function);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
/*case SYMBOL_REMOVED:
|
|
||||||
rec = (ProgramChangeRecord)ev.getChangeRecord(i);
|
|
||||||
addr = (Address)rec.getObject();
|
|
||||||
function = currentProgram.getListing().getFunctionAt(addr);
|
|
||||||
if (function != null) {
|
|
||||||
provider.functionChanged(function);
|
|
||||||
}
|
|
||||||
break;*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void programActivated(Program program) {
|
protected void programActivated(Program program) {
|
||||||
program.addListener(this);
|
program.addListener(domainObjectListener);
|
||||||
provider.programOpened(program);
|
provider.programOpened(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void programDeactivated(Program program) {
|
protected void programDeactivated(Program program) {
|
||||||
program.removeListener(this);
|
program.removeListener(domainObjectListener);
|
||||||
provider.programClosed();
|
provider.programClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
import ghidra.app.events.*;
|
import ghidra.app.events.*;
|
||||||
import ghidra.app.nav.NavigationUtils;
|
import ghidra.app.nav.NavigationUtils;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.plugintool.PluginEvent;
|
import ghidra.framework.plugintool.PluginEvent;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.PluginEventListener;
|
import ghidra.framework.plugintool.util.PluginEventListener;
|
||||||
|
@ -38,13 +39,14 @@ import ghidra.util.Swing;
|
||||||
* Base class for GraphDisplay listeners whose nodes represent addresses.
|
* Base class for GraphDisplay listeners whose nodes represent addresses.
|
||||||
*/
|
*/
|
||||||
public abstract class AddressBasedGraphDisplayListener
|
public abstract class AddressBasedGraphDisplayListener
|
||||||
implements GraphDisplayListener, PluginEventListener, DomainObjectListener {
|
implements GraphDisplayListener, PluginEventListener {
|
||||||
|
|
||||||
protected PluginTool tool;
|
protected PluginTool tool;
|
||||||
protected GraphDisplay graphDisplay;
|
protected GraphDisplay graphDisplay;
|
||||||
protected Program program;
|
protected Program program;
|
||||||
private SymbolTable symbolTable;
|
private SymbolTable symbolTable;
|
||||||
private String name;
|
private String name;
|
||||||
|
private DomainObjectListener domainObjectListener;
|
||||||
private static AtomicInteger instanceCount = new AtomicInteger(1);
|
private static AtomicInteger instanceCount = new AtomicInteger(1);
|
||||||
|
|
||||||
public AddressBasedGraphDisplayListener(PluginTool tool, Program program,
|
public AddressBasedGraphDisplayListener(PluginTool tool, Program program,
|
||||||
|
@ -55,7 +57,46 @@ public abstract class AddressBasedGraphDisplayListener
|
||||||
this.graphDisplay = display;
|
this.graphDisplay = display;
|
||||||
name = getClass().getSimpleName() + instanceCount.getAndAdd(1);
|
name = getClass().getSimpleName() + instanceCount.getAndAdd(1);
|
||||||
tool.addListenerForAllPluginEvents(this);
|
tool.addListenerForAllPluginEvents(this);
|
||||||
program.addListener(this);
|
domainObjectListener = createDomainObjectListener();
|
||||||
|
program.addListener(domainObjectListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
|
// @formatter:off
|
||||||
|
return new DomainObjectListenerBuilder(this)
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(SYMBOL_ADDED).call(this::handleSymbolAdded)
|
||||||
|
.each(SYMBOL_RENAMED).call(this::handleSymbolRenamed)
|
||||||
|
.each(SYMBOL_REMOVED).call(this::handleSymbolRemoved)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSymbolAdded(ProgramChangeRecord rec) {
|
||||||
|
Symbol symbol = (Symbol) rec.getNewValue();
|
||||||
|
AttributedVertex vertex = getVertex(rec.getStart());
|
||||||
|
if (vertex != null) {
|
||||||
|
graphDisplay.updateVertexName(vertex, symbol.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSymbolRenamed(ProgramChangeRecord rec) {
|
||||||
|
Symbol symbol = (Symbol) rec.getObject();
|
||||||
|
AttributedVertex vertex = getVertex(rec.getStart());
|
||||||
|
if (vertex != null) {
|
||||||
|
graphDisplay.updateVertexName(vertex, symbol.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSymbolRemoved(ProgramChangeRecord rec) {
|
||||||
|
Address address = rec.getStart();
|
||||||
|
AttributedVertex vertex = getVertex(address);
|
||||||
|
if (vertex == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Symbol symbol = program.getSymbolTable().getPrimarySymbol(address);
|
||||||
|
String displayName = symbol == null ? address.toString() : symbol.getName();
|
||||||
|
graphDisplay.updateVertexName(vertex, displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -189,52 +230,10 @@ public abstract class AddressBasedGraphDisplayListener
|
||||||
return p == program;
|
return p == program;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
|
||||||
if (!(ev.contains(SYMBOL_ADDED, SYMBOL_RENAMED, SYMBOL_REMOVED))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (DomainObjectChangeRecord record : ev) {
|
|
||||||
if (record instanceof ProgramChangeRecord) {
|
|
||||||
ProgramChangeRecord programRecord = (ProgramChangeRecord) record;
|
|
||||||
Address address = programRecord.getStart();
|
|
||||||
|
|
||||||
if (record.getEventType() == ProgramEvent.SYMBOL_RENAMED) {
|
|
||||||
handleSymbolAddedOrRenamed(address, (Symbol) programRecord.getObject());
|
|
||||||
}
|
|
||||||
else if (record.getEventType() == ProgramEvent.SYMBOL_ADDED) {
|
|
||||||
handleSymbolAddedOrRenamed(address, (Symbol) programRecord.getNewValue());
|
|
||||||
}
|
|
||||||
else if (record.getEventType() == ProgramEvent.SYMBOL_REMOVED) {
|
|
||||||
handleSymbolRemoved(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSymbolAddedOrRenamed(Address address, Symbol symbol) {
|
|
||||||
AttributedVertex vertex = getVertex(address);
|
|
||||||
if (vertex == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
graphDisplay.updateVertexName(vertex, symbol.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSymbolRemoved(Address address) {
|
|
||||||
AttributedVertex vertex = getVertex(address);
|
|
||||||
if (vertex == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Symbol symbol = program.getSymbolTable().getPrimarySymbol(address);
|
|
||||||
String displayName = symbol == null ? address.toString() : symbol.getName();
|
|
||||||
graphDisplay.updateVertexName(vertex, displayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
Swing.runLater(() -> tool.removeListenerForAllPluginEvents(this));
|
Swing.runLater(() -> tool.removeListenerForAllPluginEvents(this));
|
||||||
program.removeListener(this);
|
program.removeListener(domainObjectListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,11 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
|
||||||
tool = null;
|
tool = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentHidden() {
|
||||||
|
setProgram(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the Main panel.
|
* Define the Main panel.
|
||||||
*
|
*
|
||||||
|
@ -130,6 +135,10 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAddress(Address address) {
|
void setAddress(Address address) {
|
||||||
|
if (program == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Instruction instruction = getInstruction(address);
|
Instruction instruction = getInstruction(address);
|
||||||
myAddr = instruction != null ? instruction.getMinAddress() : address;
|
myAddr = instruction != null ? instruction.getMinAddress() : address;
|
||||||
SleighDebugLogger debug = null;
|
SleighDebugLogger debug = null;
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.progmgr;
|
package ghidra.app.plugin.core.progmgr;
|
||||||
|
|
||||||
|
import static ghidra.framework.model.DomainObjectEvent.*;
|
||||||
|
|
||||||
import java.rmi.NoSuchObjectException;
|
import java.rmi.NoSuchObjectException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
@ -41,7 +43,7 @@ import ghidra.util.task.TaskLauncher;
|
||||||
/**
|
/**
|
||||||
* Class for tracking open programs in the tool.
|
* Class for tracking open programs in the tool.
|
||||||
*/
|
*/
|
||||||
class MultiProgramManager implements DomainObjectListener, TransactionListener {
|
class MultiProgramManager implements TransactionListener {
|
||||||
|
|
||||||
private ProgramManagerPlugin plugin;
|
private ProgramManagerPlugin plugin;
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
|
@ -60,6 +62,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
|
||||||
// result in a second copy of that program being opened. This is safe because program opens
|
// result in a second copy of that program being opened. This is safe because program opens
|
||||||
// and closes are all done from the Swing thread.
|
// and closes are all done from the Swing thread.
|
||||||
private Map<Program, ProgramInfo> programMap = new ConcurrentHashMap<>();
|
private Map<Program, ProgramInfo> programMap = new ConcurrentHashMap<>();
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
MultiProgramManager(ProgramManagerPlugin programManagerPlugin) {
|
MultiProgramManager(ProgramManagerPlugin programManagerPlugin) {
|
||||||
this.plugin = programManagerPlugin;
|
this.plugin = programManagerPlugin;
|
||||||
|
@ -95,7 +98,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
|
||||||
|
|
||||||
fireOpenEvents(p);
|
fireOpenEvents(p);
|
||||||
|
|
||||||
p.addListener(this);
|
p.addListener(domainObjectListener);
|
||||||
p.addTransactionListener(this);
|
p.addTransactionListener(this);
|
||||||
}
|
}
|
||||||
else if (!oldInfo.visible && state != ProgramManager.OPEN_HIDDEN) {
|
else if (!oldInfo.visible && state != ProgramManager.OPEN_HIDDEN) {
|
||||||
|
@ -112,7 +115,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
|
||||||
fireActivatedEvent(null);
|
fireActivatedEvent(null);
|
||||||
|
|
||||||
for (Program p : programMap.keySet()) {
|
for (Program p : programMap.keySet()) {
|
||||||
p.removeListener(this);
|
p.removeListener(domainObjectListener);
|
||||||
p.removeTransactionListener(this);
|
p.removeTransactionListener(this);
|
||||||
fireCloseEvents(p);
|
fireCloseEvents(p);
|
||||||
p.release(tool);
|
p.release(tool);
|
||||||
|
@ -141,7 +144,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
|
||||||
else {
|
else {
|
||||||
p.removeTransactionListener(this);
|
p.removeTransactionListener(this);
|
||||||
programMap.remove(p);
|
programMap.remove(p);
|
||||||
p.removeListener(this);
|
p.removeListener(domainObjectListener);
|
||||||
if (info == currentInfo) {
|
if (info == currentInfo) {
|
||||||
ProgramInfo newCurrent = findNextCurrent();
|
ProgramInfo newCurrent = findNextCurrent();
|
||||||
setCurrentProgram(newCurrent);
|
setCurrentProgram(newCurrent);
|
||||||
|
@ -287,13 +290,26 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
|
||||||
new ProgramVisibilityChangePluginEvent(pluginName, program, isVisible));
|
new ProgramVisibilityChangePluginEvent(pluginName, program, isVisible));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
DomainObjectListener createDomainObjectListener() {
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
// @formatter:off
|
||||||
if (!(ev.getSource() instanceof Program program)) {
|
return new DomainObjectListenerBuilder(this)
|
||||||
return;
|
.any(ERROR).terminate(this::handleError)
|
||||||
|
.each(FILE_CHANGED).call(this::handleFileChanged)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.forEach(DomainObjectEvent.FILE_CHANGED, r -> {
|
private void handleError(DomainObjectChangedEvent event) {
|
||||||
|
DomainObjectChangeRecord rec = event.findFirst(ERROR);
|
||||||
|
if (event.getSource() instanceof Program program) {
|
||||||
|
String msg = getErrorMessage(program, (Throwable) rec.getNewValue());
|
||||||
|
Msg.showError(this, tool.getToolFrame(), "Severe Error Condition", msg);
|
||||||
|
removeProgram(program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFileChanged(DomainObjectChangedEvent event, DomainObjectChangeRecord rec) {
|
||||||
|
if (event.getSource() instanceof Program program) {
|
||||||
ProgramInfo info = getInfo(program);
|
ProgramInfo info = getInfo(program);
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
info.programSavedAs(); // updates info to new domain file
|
info.programSavedAs(); // updates info to new domain file
|
||||||
|
@ -302,18 +318,6 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
|
||||||
String name = program.getDomainFile().toString();
|
String name = program.getDomainFile().toString();
|
||||||
tool.setSubTitle(name);
|
tool.setSubTitle(name);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (ev.contains(DomainObjectEvent.ERROR)) {
|
|
||||||
for (DomainObjectChangeRecord docr : ev) {
|
|
||||||
EventType eventType = docr.getEventType();
|
|
||||||
if (eventType == DomainObjectEvent.ERROR) {
|
|
||||||
String msg = getErrorMessage(program, (Throwable) docr.getNewValue());
|
|
||||||
Msg.showError(this, tool.getToolFrame(), "Severe Error Condition", msg);
|
|
||||||
removeProgram(program);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.symboltree;
|
package ghidra.app.plugin.core.symboltree;
|
||||||
|
|
||||||
|
import static ghidra.framework.model.DomainObjectEvent.*;
|
||||||
import static ghidra.program.util.ProgramEvent.*;
|
import static ghidra.program.util.ProgramEvent.*;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
@ -38,7 +39,8 @@ import docking.widgets.tree.tasks.GTreeBulkTask;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.plugin.core.symboltree.actions.*;
|
import ghidra.app.plugin.core.symboltree.actions.*;
|
||||||
import ghidra.app.plugin.core.symboltree.nodes.*;
|
import ghidra.app.plugin.core.symboltree.nodes.*;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -76,6 +78,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
* 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<GTreeTask> bufferedTasks = new ArrayList<>();
|
||||||
|
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", () -> {
|
||||||
|
|
||||||
|
@ -101,8 +104,6 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
tree.runTask(new BulkWorkTask(tree, copiedTasks));
|
tree.runTask(new BulkWorkTask(tree, copiedTasks));
|
||||||
});
|
});
|
||||||
|
|
||||||
private Map<Program, GTreeState> treeStateMap = new HashMap<>();
|
|
||||||
|
|
||||||
public SymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin) {
|
public SymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin) {
|
||||||
super(tool, NAME, plugin.getName());
|
super(tool, NAME, plugin.getName());
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
@ -110,7 +111,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
setIcon(ICON);
|
setIcon(ICON);
|
||||||
addToToolbar();
|
addToToolbar();
|
||||||
|
|
||||||
domainObjectListener = new SymbolTreeProviderDomainObjectListener();
|
domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
localClipboard = new Clipboard(NAME);
|
localClipboard = new Clipboard(NAME);
|
||||||
|
|
||||||
|
@ -425,6 +426,10 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
tree.refilterLater();
|
tree.refilterLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void symbolChanged(Symbol symbol) {
|
||||||
|
symbolChanged(symbol, symbol.getName());
|
||||||
|
}
|
||||||
|
|
||||||
private void symbolChanged(Symbol symbol, String oldName) {
|
private void symbolChanged(Symbol symbol, String oldName) {
|
||||||
addTask(new SymbolChangedTask(tree, symbol, oldName));
|
addTask(new SymbolChangedTask(tree, symbol, oldName));
|
||||||
}
|
}
|
||||||
|
@ -529,6 +534,82 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// EventHandling
|
||||||
|
//==================================================================================================
|
||||||
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
|
// @formatter:off
|
||||||
|
return new DomainObjectListenerBuilder(this)
|
||||||
|
.ignoreWhen(this::ignoreEvents)
|
||||||
|
.any(RESTORED).terminate(this::rebuildTree)
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(SYMBOL_RENAMED).call(this::processSymbolRenamed)
|
||||||
|
.each(SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED).call(this::processSymbolChanged)
|
||||||
|
.each(FUNCTION_CHANGED).call(this::processFunctionChanged)
|
||||||
|
.each(SYMBOL_ADDED).call(this::processSymbolAdded)
|
||||||
|
.each(SYMBOL_REMOVED).call(this::processSymbolRemoved)
|
||||||
|
.each(EXTERNAL_ENTRY_ADDED, EXTERNAL_ENTRY_REMOVED)
|
||||||
|
.call(this::processExternalEntryChanged)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSymbolAdded(ProgramChangeRecord pcr) {
|
||||||
|
symbolAdded((Symbol) pcr.getNewValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSymbolRemoved(ProgramChangeRecord pcr) {
|
||||||
|
symbolRemoved((Symbol) pcr.getObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processFunctionChanged(ProgramChangeRecord pcr) {
|
||||||
|
Function function = (Function) pcr.getObject();
|
||||||
|
Symbol symbol = function.getSymbol();
|
||||||
|
symbolChanged(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSymbolChanged(ProgramChangeRecord pcr) {
|
||||||
|
Symbol symbol = (Symbol) pcr.getObject();
|
||||||
|
symbolChanged(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSymbolRenamed(ProgramChangeRecord pcr) {
|
||||||
|
Symbol symbol = (Symbol) pcr.getObject();
|
||||||
|
String oldName = (String) pcr.getOldValue();
|
||||||
|
symbolChanged(symbol, oldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processExternalEntryChanged(ProgramChangeRecord pcr) {
|
||||||
|
Address address = pcr.getStart();
|
||||||
|
SymbolTable symbolTable = program.getSymbolTable();
|
||||||
|
Symbol[] symbols = symbolTable.getSymbols(address);
|
||||||
|
for (Symbol symbol : symbols) {
|
||||||
|
symbolChanged(symbol, symbol.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean ignoreEvents() {
|
||||||
|
if (!isVisible()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return treeIsCollapsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean treeIsCollapsed() {
|
||||||
|
// note: the root's children are visible by default
|
||||||
|
GTreeNode root = tree.getViewRoot();
|
||||||
|
if (!root.isExpanded()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
List<GTreeNode> children = root.getChildren();
|
||||||
|
for (GTreeNode node : children) {
|
||||||
|
if (node.isExpanded()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -665,93 +746,4 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SymbolTreeProviderDomainObjectListener implements DomainObjectListener {
|
|
||||||
@Override
|
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent event) {
|
|
||||||
|
|
||||||
if (ignoreEvents()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.contains(DomainObjectEvent.RESTORED)) {
|
|
||||||
rebuildTree();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!event.contains(SYMBOL_RENAMED, SYMBOL_DATA_CHANGED, SYMBOL_SCOPE_CHANGED,
|
|
||||||
FUNCTION_CHANGED, SYMBOL_ADDED, SYMBOL_REMOVED, EXTERNAL_ENTRY_ADDED,
|
|
||||||
EXTERNAL_ENTRY_REMOVED)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int recordCount = event.numRecords();
|
|
||||||
for (int i = 0; i < recordCount; i++) {
|
|
||||||
DomainObjectChangeRecord rec = event.getChangeRecord(i);
|
|
||||||
EventType eventType = rec.getEventType();
|
|
||||||
|
|
||||||
Object affectedObject = null;
|
|
||||||
if (rec instanceof ProgramChangeRecord) {
|
|
||||||
affectedObject = ((ProgramChangeRecord) rec).getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventType == SYMBOL_RENAMED) {
|
|
||||||
Symbol symbol = (Symbol) affectedObject;
|
|
||||||
symbolChanged(symbol, (String) rec.getOldValue());
|
|
||||||
}
|
|
||||||
else if (eventType == SYMBOL_DATA_CHANGED || eventType == SYMBOL_SCOPE_CHANGED ||
|
|
||||||
eventType == FUNCTION_CHANGED) {
|
|
||||||
|
|
||||||
Symbol symbol = null;
|
|
||||||
if (affectedObject instanceof Symbol) {
|
|
||||||
symbol = (Symbol) affectedObject;
|
|
||||||
}
|
|
||||||
else if (affectedObject instanceof Namespace) {
|
|
||||||
symbol = ((Namespace) affectedObject).getSymbol();
|
|
||||||
}
|
|
||||||
|
|
||||||
symbolChanged(symbol, symbol.getName());
|
|
||||||
}
|
|
||||||
else if (eventType == SYMBOL_ADDED) {
|
|
||||||
Symbol symbol = (Symbol) rec.getNewValue();
|
|
||||||
symbolAdded(symbol);
|
|
||||||
}
|
|
||||||
else if (eventType == SYMBOL_REMOVED) {
|
|
||||||
Symbol symbol = (Symbol) affectedObject;
|
|
||||||
symbolRemoved(symbol);
|
|
||||||
}
|
|
||||||
else if (eventType == EXTERNAL_ENTRY_ADDED || eventType == EXTERNAL_ENTRY_REMOVED) {
|
|
||||||
ProgramChangeRecord programChangeRecord = (ProgramChangeRecord) rec;
|
|
||||||
Address address = programChangeRecord.getStart();
|
|
||||||
SymbolTable symbolTable = program.getSymbolTable();
|
|
||||||
Symbol[] symbols = symbolTable.getSymbols(address);
|
|
||||||
for (Symbol symbol : symbols) {
|
|
||||||
symbolChanged(symbol, symbol.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean ignoreEvents() {
|
|
||||||
if (!isVisible()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return treeIsCollapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean treeIsCollapsed() {
|
|
||||||
// note: the root's children are visible by default
|
|
||||||
GTreeNode root = tree.getViewRoot();
|
|
||||||
if (!root.isExpanded()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
List<GTreeNode> children = root.getChildren();
|
|
||||||
for (GTreeNode node : children) {
|
|
||||||
if (node.isExpanded()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ import ghidra.app.plugin.core.symboltree.actions.*;
|
||||||
import ghidra.app.services.BlockModelService;
|
import ghidra.app.services.BlockModelService;
|
||||||
import ghidra.app.services.GoToService;
|
import ghidra.app.services.GoToService;
|
||||||
import ghidra.app.util.SymbolInspector;
|
import ghidra.app.util.SymbolInspector;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.DomainObjectListener;
|
||||||
|
import ghidra.framework.model.DomainObjectListenerBuilder;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
@ -43,7 +44,6 @@ import ghidra.program.model.listing.Data;
|
||||||
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.ProgramChangeRecord;
|
import ghidra.program.util.ProgramChangeRecord;
|
||||||
import ghidra.program.util.ProgramEvent;
|
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.SelectionNavigationAction;
|
import ghidra.util.table.SelectionNavigationAction;
|
||||||
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
||||||
|
@ -73,7 +73,7 @@ import resources.Icons;
|
||||||
eventsConsumed = { ProgramActivatedPluginEvent.class }
|
eventsConsumed = { ProgramActivatedPluginEvent.class }
|
||||||
)
|
)
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
public class SymbolTablePlugin extends Plugin {
|
||||||
|
|
||||||
final static Cursor WAIT_CURSOR = new Cursor(Cursor.WAIT_CURSOR);
|
final static Cursor WAIT_CURSOR = new Cursor(Cursor.WAIT_CURSOR);
|
||||||
final static Cursor NORM_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR);
|
final static Cursor NORM_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR);
|
||||||
|
@ -94,6 +94,8 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
||||||
private BlockModelService blockModelService;
|
private BlockModelService blockModelService;
|
||||||
private SwingUpdateManager swingMgr;
|
private SwingUpdateManager swingMgr;
|
||||||
|
|
||||||
|
private DomainObjectListener domainObjectListener = createDomainObjectListener();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A worker that will process domain object change event work off of the Swing thread. This
|
* A worker that will process domain object change event work off of the Swing thread. This
|
||||||
* solves the issue of db lock contention that can happen during analysis while this class
|
* solves the issue of db lock contention that can happen during analysis while this class
|
||||||
|
@ -147,7 +149,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
||||||
refProvider = null;
|
refProvider = null;
|
||||||
}
|
}
|
||||||
if (currentProgram != null) {
|
if (currentProgram != null) {
|
||||||
currentProgram.removeListener(this);
|
currentProgram.removeListener(domainObjectListener);
|
||||||
currentProgram = null;
|
currentProgram = null;
|
||||||
}
|
}
|
||||||
gotoService = null;
|
gotoService = null;
|
||||||
|
@ -178,7 +180,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
||||||
|
|
||||||
if (oldProg != null) {
|
if (oldProg != null) {
|
||||||
inspector.setProgram(null);
|
inspector.setProgram(null);
|
||||||
oldProg.removeListener(this);
|
oldProg.removeListener(domainObjectListener);
|
||||||
domainObjectWorker.clearAllJobs();
|
domainObjectWorker.clearAllJobs();
|
||||||
symProvider.setProgram(null, inspector);
|
symProvider.setProgram(null, inspector);
|
||||||
refProvider.setProgram(null, inspector);
|
refProvider.setProgram(null, inspector);
|
||||||
|
@ -186,7 +188,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
||||||
}
|
}
|
||||||
currentProgram = newProg;
|
currentProgram = newProg;
|
||||||
if (newProg != null) {
|
if (newProg != null) {
|
||||||
currentProgram.addListener(this);
|
currentProgram.addListener(domainObjectListener);
|
||||||
inspector.setProgram(currentProgram);
|
inspector.setProgram(currentProgram);
|
||||||
symProvider.setProgram(currentProgram, inspector);
|
symProvider.setProgram(currentProgram, inspector);
|
||||||
refProvider.setProgram(currentProgram, inspector);
|
refProvider.setProgram(currentProgram, inspector);
|
||||||
|
@ -206,99 +208,85 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener {
|
||||||
refProvider.reload();
|
refProvider.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private DomainObjectListener createDomainObjectListener() {
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
// @formatter:off
|
||||||
if (!symProvider.isVisible() && !refProvider.isVisible()) {
|
return new DomainObjectListenerBuilder(this)
|
||||||
return;
|
.ignoreWhen(() -> !symProvider.isVisible() && !refProvider.isVisible())
|
||||||
|
.any(RESTORED, MEMORY_BLOCK_ADDED, MEMORY_BLOCK_REMOVED)
|
||||||
|
.terminate(this::reload)
|
||||||
|
.with(ProgramChangeRecord.class)
|
||||||
|
.each(CODE_ADDED, CODE_REMOVED)
|
||||||
|
.call(this::codeAddedRemoved)
|
||||||
|
.each(SYMBOL_ADDED)
|
||||||
|
.call(this::symbolAdded)
|
||||||
|
.each(SYMBOL_REMOVED)
|
||||||
|
.call(this::symbolRemoved)
|
||||||
|
.each(SYMBOL_RENAMED, SYMBOL_SCOPE_CHANGED, SYMBOL_DATA_CHANGED)
|
||||||
|
.call(this::symbolChanged)
|
||||||
|
.each(SYMBOL_SOURCE_CHANGED)
|
||||||
|
.call(this::symbolSourceChanged)
|
||||||
|
.each(SYMBOL_PRIMARY_STATE_CHANGED)
|
||||||
|
.call(this::symbolPrimaryStateChanged)
|
||||||
|
.each(REFERENCE_ADDED)
|
||||||
|
.call(this::referenceAdded)
|
||||||
|
.each(REFERENCE_REMOVED)
|
||||||
|
.call(this::referenceRemoved)
|
||||||
|
.each(EXTERNAL_ENTRY_ADDED, EXTERNAL_ENTRY_REMOVED)
|
||||||
|
.call(this::extenalEntryAddedRemoved)
|
||||||
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.contains(RESTORED, MEMORY_BLOCK_ADDED, MEMORY_BLOCK_REMOVED)) {
|
private void codeAddedRemoved(ProgramChangeRecord rec) {
|
||||||
reload();
|
if (rec.getNewValue() instanceof Data data) {
|
||||||
return;
|
domainObjectWorker.schedule(new CodeAddedRemoveJob(currentProgram, rec.getStart()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int eventCnt = ev.numRecords();
|
private void symbolAdded(ProgramChangeRecord rec) {
|
||||||
for (int i = 0; i < eventCnt; ++i) {
|
|
||||||
DomainObjectChangeRecord doRecord = ev.getChangeRecord(i);
|
|
||||||
|
|
||||||
EventType eventType = doRecord.getEventType();
|
|
||||||
if (!(doRecord instanceof ProgramChangeRecord rec)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (eventType instanceof ProgramEvent type) {
|
|
||||||
switch (type) {
|
|
||||||
case CODE_ADDED:
|
|
||||||
case CODE_REMOVED:
|
|
||||||
if (rec.getNewValue() instanceof Data) {
|
|
||||||
domainObjectWorker.schedule(
|
|
||||||
new CodeAddedRemoveJob(currentProgram, rec.getStart()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SYMBOL_ADDED:
|
|
||||||
// NOTE: DOCR_SYMBOL_ADDED events are never generated for reference-based
|
// NOTE: DOCR_SYMBOL_ADDED events are never generated for reference-based
|
||||||
// dynamic label symbols. Reference events must be used to check for existence
|
// dynamic label symbols. Reference events must be used to check for existence
|
||||||
// of a dynamic label symbol.
|
// of a dynamic label symbol.
|
||||||
Symbol symbol = (Symbol) rec.getNewValue();
|
Symbol symbol = (Symbol) rec.getNewValue();
|
||||||
domainObjectWorker.schedule(new SymbolAddedJob(currentProgram, symbol));
|
domainObjectWorker.schedule(new SymbolAddedJob(currentProgram, symbol));
|
||||||
break;
|
}
|
||||||
|
|
||||||
case SYMBOL_REMOVED:
|
|
||||||
|
|
||||||
|
private void symbolRemoved(ProgramChangeRecord rec) {
|
||||||
Address removeAddr = rec.getStart();
|
Address removeAddr = rec.getStart();
|
||||||
Long symbolID = (Long) rec.getNewValue();
|
Long symbolID = (Long) rec.getNewValue();
|
||||||
domainObjectWorker.schedule(
|
domainObjectWorker.schedule(new SymbolRemovedJob(currentProgram, removeAddr, symbolID));
|
||||||
new SymbolRemovedJob(currentProgram, removeAddr, symbolID));
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case SYMBOL_RENAMED:
|
private void symbolChanged(ProgramChangeRecord rec) {
|
||||||
case SYMBOL_SCOPE_CHANGED:
|
Symbol symbol = (Symbol) rec.getObject();
|
||||||
case SYMBOL_DATA_CHANGED:
|
|
||||||
|
|
||||||
symbol = (Symbol) rec.getObject();
|
|
||||||
domainObjectWorker.schedule(new SymbolChangedJob(currentProgram, symbol));
|
domainObjectWorker.schedule(new SymbolChangedJob(currentProgram, symbol));
|
||||||
break;
|
}
|
||||||
|
|
||||||
case SYMBOL_SOURCE_CHANGED:
|
private void symbolSourceChanged(ProgramChangeRecord rec) {
|
||||||
|
Symbol symbol = (Symbol) rec.getObject();
|
||||||
|
domainObjectWorker.schedule(new SymbolSourceChangedJob(currentProgram, symbol));
|
||||||
|
}
|
||||||
|
|
||||||
symbol = (Symbol) rec.getObject();
|
private void symbolPrimaryStateChanged(ProgramChangeRecord rec) {
|
||||||
domainObjectWorker
|
Symbol symbol = (Symbol) rec.getNewValue();
|
||||||
.schedule(new SymbolSourceChangedJob(currentProgram, symbol));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SYMBOL_PRIMARY_STATE_CHANGED:
|
|
||||||
|
|
||||||
symbol = (Symbol) rec.getNewValue();
|
|
||||||
Symbol oldPrimarySymbol = (Symbol) rec.getOldValue();
|
Symbol oldPrimarySymbol = (Symbol) rec.getOldValue();
|
||||||
domainObjectWorker.schedule(
|
domainObjectWorker
|
||||||
new SymbolSetAsPrimaryJob(currentProgram, symbol, oldPrimarySymbol));
|
.schedule(new SymbolSetAsPrimaryJob(currentProgram, symbol, oldPrimarySymbol));
|
||||||
break;
|
}
|
||||||
|
|
||||||
case SYMBOL_ASSOCIATION_ADDED:
|
|
||||||
case SYMBOL_ASSOCIATION_REMOVED:
|
|
||||||
break;
|
|
||||||
case REFERENCE_ADDED:
|
|
||||||
|
|
||||||
|
private void referenceAdded(ProgramChangeRecord rec) {
|
||||||
Reference ref = (Reference) rec.getObject();
|
Reference ref = (Reference) rec.getObject();
|
||||||
domainObjectWorker.schedule(new ReferenceAddedJob(currentProgram, ref));
|
domainObjectWorker.schedule(new ReferenceAddedJob(currentProgram, ref));
|
||||||
break;
|
}
|
||||||
|
|
||||||
case REFERENCE_REMOVED:
|
private void referenceRemoved(ProgramChangeRecord rec) {
|
||||||
|
Reference ref = (Reference) rec.getObject();
|
||||||
ref = (Reference) rec.getObject();
|
|
||||||
domainObjectWorker.schedule(new ReferenceRemovedJob(currentProgram, ref));
|
domainObjectWorker.schedule(new ReferenceRemovedJob(currentProgram, ref));
|
||||||
break;
|
}
|
||||||
|
|
||||||
case EXTERNAL_ENTRY_ADDED:
|
|
||||||
case EXTERNAL_ENTRY_REMOVED:
|
|
||||||
|
|
||||||
|
private void extenalEntryAddedRemoved(ProgramChangeRecord rec) {
|
||||||
Address address = rec.getStart();
|
Address address = rec.getStart();
|
||||||
domainObjectWorker
|
domainObjectWorker.schedule(new ExternalEntryChangedJob(currentProgram, address));
|
||||||
.schedule(new ExternalEntryChangedJob(currentProgram, address));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Program getProgram() {
|
Program getProgram() {
|
||||||
|
|
|
@ -0,0 +1,412 @@
|
||||||
|
/* ###
|
||||||
|
* 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.framework.model;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import utilities.util.reflection.ReflectionUtilities;
|
||||||
|
import utility.function.Callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for creating a compact and efficient {@link DomainObjectListener}s. See
|
||||||
|
* {@link DomainObjectListenerBuilder} for full documentation.
|
||||||
|
*
|
||||||
|
* @param <R> The DomainObjectChangeRecord type
|
||||||
|
* @param <B> The AbstractDomainObjectListeBuilder type (the only difference is R, the record type)
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class AbstractDomainObjectListenerBuilder<R extends DomainObjectChangeRecord, B extends AbstractDomainObjectListenerBuilder<R, B>> {
|
||||||
|
private String name;
|
||||||
|
private BooleanSupplier ignoreCheck;
|
||||||
|
private List<EventTrigger> terminateList = new ArrayList<>();
|
||||||
|
private List<EventTrigger> onAnyList = new ArrayList<>();
|
||||||
|
private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap =
|
||||||
|
new HashMap<>();
|
||||||
|
|
||||||
|
private Class<? extends DomainObjectChangeRecord> activeRecordType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a builder with the given recordClass as the default record class
|
||||||
|
* @param name the name of the client class that created this builder
|
||||||
|
* @param recordClass the class of event records consumers will be using in any calls that
|
||||||
|
* take a consumer
|
||||||
|
*/
|
||||||
|
public AbstractDomainObjectListenerBuilder(String name, Class<R> recordClass) {
|
||||||
|
this.name = name;
|
||||||
|
activeRecordType = recordClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name that will be associated with the domainObjectListener. this is for
|
||||||
|
* debugging purposes so that you can tell where this listener came from (since it is
|
||||||
|
* no longer implemented by the client class)
|
||||||
|
* @return the name assigned to this builder (and ultimately the listener)
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract B self();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a boolean supplier that can be checked to see if the client is in a state where
|
||||||
|
* they don't want events to be processed at this time.
|
||||||
|
* @param supplier the boolean supplier that if returns true, events are not processed
|
||||||
|
* @return this builder (for chaining)
|
||||||
|
*/
|
||||||
|
public B ignoreWhen(BooleanSupplier supplier) {
|
||||||
|
this.ignoreCheck = supplier;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows for specifying multiple event types that if the event contains any records with
|
||||||
|
* and of the given types, then a callback or callback with terminate will be triggered,
|
||||||
|
* depending on if the next builder operation is either a call or terminate respectively.
|
||||||
|
* @param eventTypes the list of events to trigger on
|
||||||
|
* @return A sub-builder for specifying the call or call with terminate
|
||||||
|
*/
|
||||||
|
public AnyBuilder any(EventType... eventTypes) {
|
||||||
|
return new AnyBuilder(eventTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows for specifying multiple event types that for each record with one of the specified
|
||||||
|
* types, the follow on consumer will be called.
|
||||||
|
* @param eventTypes the list of events to trigger on
|
||||||
|
* @return A sub-builder for specifying the consumer to be used for records with any of
|
||||||
|
* these types
|
||||||
|
*/
|
||||||
|
public EachBuilder each(EventType... eventTypes) {
|
||||||
|
return new EachBuilder(eventTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows for specifying a new record type that any follow on consumers will use for any
|
||||||
|
* defined "each" handlers.
|
||||||
|
* @param <R2> the new record type
|
||||||
|
* @param <B2> the new builder type that expects consumers of the new record type
|
||||||
|
* @param clazz the class of the new record type
|
||||||
|
* @return this builder with its consumer record type changed
|
||||||
|
*/
|
||||||
|
public <R2 extends DomainObjectChangeRecord, B2 extends AbstractDomainObjectListenerBuilder<R2, B2>> B2 with(
|
||||||
|
Class<R2> clazz) {
|
||||||
|
|
||||||
|
activeRecordType = clazz;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
B2 newSelf = (B2) self();
|
||||||
|
return newSelf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and returns a new DomainObjectEventHandler
|
||||||
|
* @return a new DomainObjectEventHandler from this builder
|
||||||
|
*/
|
||||||
|
public DomainObjectListener build() {
|
||||||
|
|
||||||
|
BuilderDomainObjectListener listener = new BuilderDomainObjectListener(name);
|
||||||
|
listener.setIgnoreCheck(ignoreCheck);
|
||||||
|
if (!terminateList.isEmpty()) {
|
||||||
|
listener.setTerminateList(terminateList);
|
||||||
|
}
|
||||||
|
if (!onAnyList.isEmpty()) {
|
||||||
|
listener.setOnAnyList(onAnyList);
|
||||||
|
}
|
||||||
|
if (!onEachMap.isEmpty()) {
|
||||||
|
listener.setOnEachMap(onEachMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-builder for collection eventTypes before eventually being association with a
|
||||||
|
* callback or callback with termination
|
||||||
|
*/
|
||||||
|
public class AnyBuilder {
|
||||||
|
List<EventType> eventTypeList = new ArrayList<>();
|
||||||
|
|
||||||
|
public AnyBuilder(EventType[] eventTypes) {
|
||||||
|
eventTypeList.addAll(Arrays.asList(eventTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the callback to be associated with this collection of event types.
|
||||||
|
* @param callback the callback for this collection of event types
|
||||||
|
* @return the main event builder that created this sub-builder
|
||||||
|
*/
|
||||||
|
public B call(Callback callback) {
|
||||||
|
EventType[] eventTypes = eventTypeList.toArray(new EventType[eventTypeList.size()]);
|
||||||
|
onAnyList.add(new EventTrigger(callback, eventTypes));
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the callback to be associated with this collection of event types.
|
||||||
|
* @param consumer the callback for this collection of event types
|
||||||
|
* @return the main event builder that created this sub-builder
|
||||||
|
*/
|
||||||
|
public B call(Consumer<DomainObjectChangedEvent> consumer) {
|
||||||
|
EventType[] eventTypes = eventTypeList.toArray(new EventType[eventTypeList.size()]);
|
||||||
|
onAnyList.add(new EventTrigger(consumer, eventTypes));
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the callback with termination to be associated with this collection of event
|
||||||
|
* types.
|
||||||
|
* @param callback the callback for this collection of event types
|
||||||
|
* @return the main event builder that created this sub-builder
|
||||||
|
*/
|
||||||
|
public B terminate(Callback callback) {
|
||||||
|
EventType[] eventTypes = eventTypeList.toArray(new EventType[eventTypeList.size()]);
|
||||||
|
terminateList.add(new EventTrigger(callback, eventTypes));
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the consumer with termination to be associated with this collection of event
|
||||||
|
* types. This form of terminate includes the event when performing the callback.
|
||||||
|
* @param consumer the consumer for this collection of event types
|
||||||
|
* @return the main event builder that created this sub-builder
|
||||||
|
*/
|
||||||
|
public B terminate(Consumer<DomainObjectChangedEvent> consumer) {
|
||||||
|
EventType[] eventTypes = eventTypeList.toArray(new EventType[eventTypeList.size()]);
|
||||||
|
terminateList.add(new EventTrigger(consumer, eventTypes));
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-builder for collection eventTypes before eventually being associated with a
|
||||||
|
* consumer for records with those types
|
||||||
|
*/
|
||||||
|
public class EachBuilder {
|
||||||
|
List<EventType> eventTypeList = new ArrayList<>();
|
||||||
|
|
||||||
|
public EachBuilder(EventType[] eventTypes) {
|
||||||
|
eventTypeList.addAll(Arrays.asList(eventTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the consumer to be associated with this collection of event types.
|
||||||
|
* @param consumer the consumer for this collection of event types
|
||||||
|
* @return the main event builder that created this sub-builder
|
||||||
|
*/
|
||||||
|
public B call(Consumer<R> consumer) {
|
||||||
|
TypedRecordConsumer<R> trc = new TypedRecordConsumer<R>(consumer, activeRecordType);
|
||||||
|
for (EventType eventType : eventTypeList) {
|
||||||
|
onEachMap.put(eventType, trc);
|
||||||
|
}
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the consumer to be associated with this collection of event types.
|
||||||
|
* @param biConsumer the consumer for this collection of event types
|
||||||
|
* @return the main event builder that created this sub-builder
|
||||||
|
*/
|
||||||
|
public B call(BiConsumer<DomainObjectChangedEvent, R> biConsumer) {
|
||||||
|
TypedRecordConsumer<R> trc = new TypedRecordConsumer<R>(biConsumer, activeRecordType);
|
||||||
|
for (EventType eventType : eventTypeList) {
|
||||||
|
onEachMap.put(eventType, trc);
|
||||||
|
}
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class EventTrigger implements Consumer<DomainObjectChangedEvent> {
|
||||||
|
private final Consumer<DomainObjectChangedEvent> consumer;
|
||||||
|
private final EventType[] eventTypes;
|
||||||
|
|
||||||
|
EventTrigger(Consumer<DomainObjectChangedEvent> consumer, EventType... eventTypes) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
this.eventTypes = eventTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventTrigger(Callback callback, EventType... eventTypes) {
|
||||||
|
this(e -> callback.call(), eventTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTriggered(DomainObjectChangedEvent event) {
|
||||||
|
return event.contains(eventTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(DomainObjectChangedEvent e) {
|
||||||
|
consumer.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for tracking the record classes and consumers for records of that type. Also
|
||||||
|
* contains inception information if the consumers and record classes don't match up.
|
||||||
|
*
|
||||||
|
* @param <RR> The type of record and consumer for this class.
|
||||||
|
*/
|
||||||
|
static class TypedRecordConsumer<RR>
|
||||||
|
implements BiConsumer<DomainObjectChangedEvent, DomainObjectChangeRecord> {
|
||||||
|
private Class<? extends DomainObjectChangeRecord> recordClass;
|
||||||
|
private BiConsumer<DomainObjectChangedEvent, RR> consumer;
|
||||||
|
private String inceptionInformation;
|
||||||
|
|
||||||
|
TypedRecordConsumer(Consumer<RR> consumerX,
|
||||||
|
Class<? extends DomainObjectChangeRecord> recordClass) {
|
||||||
|
this((a, b) -> consumerX.accept(b), recordClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypedRecordConsumer(BiConsumer<DomainObjectChangedEvent, RR> consumer,
|
||||||
|
Class<? extends DomainObjectChangeRecord> recordClass) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
this.recordClass = recordClass;
|
||||||
|
recordInception();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordInception() {
|
||||||
|
inceptionInformation = getInceptionFromTheFirstClassThatIsNotUsOrABuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getInceptionFromTheFirstClassThatIsNotUsOrABuilder() {
|
||||||
|
Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(getClass());
|
||||||
|
StackTraceElement[] trace =
|
||||||
|
ReflectionUtilities.filterStackTrace(t.getStackTrace(), "ListenerBuilder");
|
||||||
|
String classInfo = trace[0].toString();
|
||||||
|
return classInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void accept(DomainObjectChangedEvent event, DomainObjectChangeRecord rec) {
|
||||||
|
if (recordClass.isInstance(rec)) {
|
||||||
|
consumer.accept(event, (RR) rec);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Msg.error(this,
|
||||||
|
"Registered incorrect record class for event type: " + inceptionInformation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BuilderDomainObjectListener implements DomainObjectListener {
|
||||||
|
private String name;
|
||||||
|
private BooleanSupplier ignoreCheck = () -> false;
|
||||||
|
private List<EventTrigger> terminateList;
|
||||||
|
private List<EventTrigger> onAnyList;
|
||||||
|
private Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap;
|
||||||
|
private EventType[] eachEventTypes; // all the "onEach" event types
|
||||||
|
|
||||||
|
BuilderDomainObjectListener(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setIgnoreCheck(BooleanSupplier supplier) {
|
||||||
|
this.ignoreCheck = supplier != null ? supplier : () -> false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTerminateList(List<EventTrigger> terminatEventList) {
|
||||||
|
this.terminateList = terminatEventList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOnAnyList(List<EventTrigger> onAnyList) {
|
||||||
|
this.onAnyList = onAnyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOnEachMap(
|
||||||
|
Map<EventType, TypedRecordConsumer<? extends DomainObjectChangeRecord>> onEachMap) {
|
||||||
|
this.onEachMap = onEachMap;
|
||||||
|
eachEventTypes = onEachMap.keySet().toArray(new EventType[onEachMap.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void domainObjectChanged(DomainObjectChangedEvent event) {
|
||||||
|
// check if events are being ignored
|
||||||
|
if (ignoreCheck.getAsBoolean()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// check for terminating events first
|
||||||
|
if (terminateList != null && processTerminateList(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (onAnyList != null) {
|
||||||
|
processOnAnyList(event);
|
||||||
|
}
|
||||||
|
if (onEachMap != null) {
|
||||||
|
processOnEachMap(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given event contains any of the terminate event type triggers and calls the
|
||||||
|
* associated callback if it does and terminates the event processing for this event.
|
||||||
|
* @param event the event to process
|
||||||
|
* @return true if a terminate event type is found
|
||||||
|
*/
|
||||||
|
private boolean processTerminateList(DomainObjectChangedEvent event) {
|
||||||
|
for (EventTrigger trigger : terminateList) {
|
||||||
|
if (trigger.isTriggered(event)) {
|
||||||
|
trigger.accept(event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given event contains any of the event type triggers and calls the
|
||||||
|
* associated callback if it does.
|
||||||
|
* @param event the event to process
|
||||||
|
*/
|
||||||
|
private void processOnAnyList(DomainObjectChangedEvent event) {
|
||||||
|
for (EventTrigger trigger : onAnyList) {
|
||||||
|
if (trigger.isTriggered(event)) {
|
||||||
|
trigger.accept(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is at least one record with a type that has to be processed for each record,
|
||||||
|
* then loop through the records and call the corresponding consumer.
|
||||||
|
* @param event the event being processed
|
||||||
|
*/
|
||||||
|
private void processOnEachMap(DomainObjectChangedEvent event) {
|
||||||
|
// if lots of records, first check if any event types of interest are in the event,
|
||||||
|
// otherwise, faster to just loop through records anyway
|
||||||
|
if (event.numRecords() > onEachMap.size()) {
|
||||||
|
if (!event.contains(eachEventTypes)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DomainObjectChangeRecord record : event) {
|
||||||
|
EventType type = record.getEventType();
|
||||||
|
TypedRecordConsumer<?> typedRecordConsumer = onEachMap.get(type);
|
||||||
|
if (typedRecordConsumer != null) {
|
||||||
|
typedRecordConsumer.accept(event, record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -127,4 +127,18 @@ public class DomainObjectChangedEvent extends EventObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the first record with the given event type.
|
||||||
|
* @param eventType the event type to search for
|
||||||
|
* @return the first record with the given event type
|
||||||
|
*/
|
||||||
|
public DomainObjectChangeRecord findFirst(EventType eventType) {
|
||||||
|
for (DomainObjectChangeRecord docr : subEvents) {
|
||||||
|
if (docr.getEventType() == eventType) {
|
||||||
|
return docr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/* ###
|
||||||
|
* 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.framework.model;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import utility.function.Callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for creating a compact and efficient {@link DomainObjectListener} for
|
||||||
|
* {@link DomainObjectChangedEvent}s
|
||||||
|
* <P>
|
||||||
|
* There are three basic ways to process {@link DomainObjectChangeRecord}s within a
|
||||||
|
* {@link DomainObjectChangedEvent}.
|
||||||
|
* <P>The first way is to look for the event to contain one or more
|
||||||
|
* records of a certain type, and if it is there, do some major refresh operation, and ignore
|
||||||
|
* the remaining event records. This is can be handled with an {@link #any(EventType...)},
|
||||||
|
* followed by a {@link AnyBuilder#terminate(Callback)} or {@link AnyBuilder#terminate(Consumer)}
|
||||||
|
* if you want the event.
|
||||||
|
* <P>
|
||||||
|
* <PRE>
|
||||||
|
* new DomainObjectListenerBuilder()
|
||||||
|
* .any(DomainObjectEvent.RESTORED).call(() -> refreshAll())
|
||||||
|
* .build();
|
||||||
|
* </PRE>
|
||||||
|
*
|
||||||
|
*or if you need the event, you can use a consumer
|
||||||
|
*
|
||||||
|
* <PRE>
|
||||||
|
* new DomainObjectListenerBuilder()
|
||||||
|
* .any(DomainObjectEvent.RESTORED).call(e -> refreshAll(e))
|
||||||
|
* .build();
|
||||||
|
* </PRE>
|
||||||
|
* <P>
|
||||||
|
* The second way is to just test for presence of one or more records of a certain type, and if
|
||||||
|
* any of those types exist is the event, call a method. In this case you don't need to know the
|
||||||
|
* details of the record, only that one of the given events was fired. This can be handled using
|
||||||
|
* the {@link #any(EventType...)}, followed by a call to {@link AnyBuilder#call(Callback)} or
|
||||||
|
* {@link AnyBuilder#call(Consumer)}
|
||||||
|
* <P>
|
||||||
|
* <PRE>
|
||||||
|
* new DomainObjectListenerBuilder()
|
||||||
|
* .onAny(ProgramEvent.FUNCTION_CHANGED).call(() -> refreshFunctions())
|
||||||
|
* .build();
|
||||||
|
* </PRE>
|
||||||
|
*or if you need the event, you can use a consumer
|
||||||
|
* <PRE>
|
||||||
|
*
|
||||||
|
* new DomainObjectListenerBuilder()
|
||||||
|
* .onAny(ProgramEvent.FUNCTION_CHANGED).call(e -> refreshFunctions(e))
|
||||||
|
* .build();
|
||||||
|
* </PRE>
|
||||||
|
* <P>
|
||||||
|
* And finally, the third way is where you have to perform some processing on each record of a
|
||||||
|
* certain type. This can be done using the the {@link #each(EventType...)}, followed by the
|
||||||
|
* {@link EachBuilder#call(Consumer)} if you just want the record, or
|
||||||
|
* {@link EachBuilder#call(BiConsumer)} if you want the record and the event.
|
||||||
|
* <P>
|
||||||
|
* By default, the consumer for the "each" case is typed on DomainObjectChangeRecord. But that
|
||||||
|
* can be changed by calling {@link #with(Class)}. Once this is called the builder
|
||||||
|
* will require that all consumers being passed in will now be typed on that record
|
||||||
|
* class.
|
||||||
|
* <P>
|
||||||
|
* <PRE>
|
||||||
|
* new DomainObjectListenerBuilder()
|
||||||
|
* .each(DomainObjectEvent.PROPERTY_CHANGED).call(r -> processPropertyChanged(r))
|
||||||
|
* .withRecord(ProgramChangeRecord.class)
|
||||||
|
* .each(ProgramEvent.SYMBOL_RENANED).call(r -> symbolRenamed(r)
|
||||||
|
* .build();
|
||||||
|
*
|
||||||
|
* private void processPropertyChanged(DomainObjectChangeRecord record) {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* private void symbolRenamed(ProgramChangeRecord record) {
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* </PRE>
|
||||||
|
*
|
||||||
|
* or if you also need the event (to get the domainObject that is the event source)
|
||||||
|
*
|
||||||
|
* <PRE
|
||||||
|
* new DomainObjectListenerBuilder()
|
||||||
|
* .each(DomainObjectEvent.PROPERTY_CHANGED).call((e, r) -> processPropertyChanged(e, r))
|
||||||
|
* .withRecord(ProgramChangeRecord.class)
|
||||||
|
* .each(ProgramEvent.SYMBOL_RENANED).call((e, r) -> symbolRenamed(e, r)
|
||||||
|
* .build();
|
||||||
|
*
|
||||||
|
* private void propertyChanged(DomainObjectChangedEvent e, DomainObjectChangeRecord record) {
|
||||||
|
* Program p = (Program)e.getSource().
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* private void symbolRenamed(DomainObjectChangedEvent e, ProgramChangeRecord record) {
|
||||||
|
* Program p = (Program)e.getSource().
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* </PRE>
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class DomainObjectListenerBuilder extends
|
||||||
|
AbstractDomainObjectListenerBuilder<DomainObjectChangeRecord, DomainObjectListenerBuilder> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new builder
|
||||||
|
* @param creator the object that created this builder (usually, just pass in "this"). This
|
||||||
|
* will help with debugging event processing
|
||||||
|
*/
|
||||||
|
public DomainObjectListenerBuilder(Object creator) {
|
||||||
|
super(creator.getClass().getSimpleName(), DomainObjectChangeRecord.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DomainObjectListenerBuilder self() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,7 +16,6 @@
|
||||||
package ghidra.program.util;
|
package ghidra.program.util;
|
||||||
|
|
||||||
import ghidra.framework.model.DomainObjectChangeRecord;
|
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||||
import ghidra.framework.model.EventType;
|
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue