diff --git a/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartTableProvider.java b/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartTableProvider.java index 12052387f0..f83e1f59c4 100644 --- a/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartTableProvider.java +++ b/Ghidra/Extensions/MachineLearning/src/main/java/ghidra/machinelearning/functionfinding/FunctionStartTableProvider.java @@ -15,15 +15,18 @@ */ package ghidra.machinelearning.functionfinding; +import static ghidra.framework.model.DomainObjectEvent.*; +import static ghidra.program.util.ProgramEvent.*; + import java.awt.*; import javax.swing.*; -import ghidra.framework.model.*; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectListener; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.program.model.address.AddressSet; import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramEvent; import ghidra.util.HelpLocation; import ghidra.util.table.*; @@ -84,30 +87,11 @@ public class FunctionStartTableProvider extends ProgramAssociatedComponentProvid if (!isVisible()) { 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(); 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; - } - } - } } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java index 69b4fd2a64..1a0b51c6e7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java @@ -15,6 +15,9 @@ */ 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.util.*; import java.util.Stack; @@ -57,7 +60,7 @@ import ghidra.util.task.*; * Provides support for auto analysis tasks. * 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. @@ -137,6 +140,7 @@ public class AutoAnalysisManager implements DomainObjectListener { private List listeners = new CopyOnWriteArrayList<>(); private EventQueueID eventQueueID; + private DomainObjectListener domainObjectListener = createDomainObjectListener(); /** * 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) { this.program = program; - eventQueueID = program.createPrivateEventQueue(this, 500); + eventQueueID = program.createPrivateEventQueue(domainObjectListener, 500); program.addCloseListener(dobj -> dispose()); initializeAnalyzers(); } @@ -346,118 +350,103 @@ public class AutoAnalysisManager implements DomainObjectListener { debugOn = b; } - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { +// private void handleSymbolAddedRenamed(ProgramChangeRecord pcr) { +// // 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 +// if (pcr.getObject() != null && pcr.getObject() instanceof VariableSymbolDB) { +// break; +// } +// Symbol sym = null; +// Object newValue = pcr.getNewValue(); +// if (newValue != null && newValue instanceof Symbol) { +// sym = (Symbol) newValue; +// } +// else if (pcr.getObject() != null && pcr.getObject() instanceof Symbol) { +// sym = (Symbol) pcr.getObject(); +// } +// if (sym == null) { +// break; +// } +// SymbolType symbolType = sym.getSymbolType(); +// if ((symbolType == SymbolType.CODE || symbolType == SymbolType.FUNCTION) && +// sym.getSource() != SourceType.DEFAULT) { +// symbolTasks.notifyAdded(sym.getAddress()); +// } +// +// } + + 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; + return true; } 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 -// // split variable changed/added from SYMBOL added - change record is already different -// if (pcr.getObject() != null && -// pcr.getObject() instanceof VariableSymbolDB) { -// break; -// } -// Symbol sym = null; -// Object newValue = pcr.getNewValue(); -// if (newValue != null && newValue instanceof Symbol) { -// sym = (Symbol) newValue; -// } -// else if (pcr.getObject() != null && pcr.getObject() instanceof Symbol) { -// sym = (Symbol) pcr.getObject(); -// } -// if (sym == null) { -// break; -// } -// SymbolType symbolType = sym.getSymbolType(); -// if ((symbolType == SymbolType.CODE || symbolType == SymbolType.FUNCTION) && -// sym.getSource() != SourceType.DEFAULT) { -// symbolTasks.notifyAdded(sym.getAddress()); -// } -// break; - default: - } - } + return true; } + return ignoreChanges; } /** @@ -978,8 +967,6 @@ public class AutoAnalysisManager implements DomainObjectListener { return; // already been disposed() } - localProgram.removeListener(this); - synchronized (this) { // sync against multiple dispose calls if (service != null) { service.dispose(); @@ -1815,4 +1802,5 @@ public class AutoAnalysisManager implements DomainObjectListener { } } } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java index 5eeac62519..c7f8b1f0b1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java @@ -36,7 +36,8 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.services.*; 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.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; @@ -67,8 +68,7 @@ import resources.MultiIconBuilder; eventsProduced = { ProgramSelectionPluginEvent.class } ) //@formatter:on -public class BookmarkPlugin extends ProgramPlugin - implements DomainObjectListener, PopupActionProvider, BookmarkService { +public class BookmarkPlugin extends ProgramPlugin implements PopupActionProvider, BookmarkService { private final static int MAX_DELETE_ACTIONS = 10; @@ -88,6 +88,8 @@ public class BookmarkPlugin extends ProgramPlugin private Map bookmarkNavigators = new HashMap<>(); // maps type names to BookmarkNavigators private NavUpdater navUpdater; + private DomainObjectListener domainObjectListener = createDomainObjectListener(); + public BookmarkPlugin(PluginTool tool) { super(tool); @@ -97,6 +99,21 @@ public class BookmarkPlugin extends ProgramPlugin 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 public void readConfigState(SaveState saveState) { super.readConfigState(saveState); @@ -214,7 +231,7 @@ public class BookmarkPlugin extends ProgramPlugin markerService = null; if (currentProgram != null) { - currentProgram.removeListener(this); + currentProgram.removeListener(domainObjectListener); } currentProgram = null; super.dispose(); @@ -296,48 +313,9 @@ public class BookmarkPlugin extends ProgramPlugin navUpdater.addType(type); } - @Override - public synchronized void domainObjectChanged(DomainObjectChangedEvent event) { - - if (event.contains(RESTORED, MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED)) { - scheduleUpdate(null); - 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(); - } - } - } + public void reload() { + scheduleUpdate(null); + provider.reload(); } @Override @@ -345,12 +323,18 @@ public class BookmarkPlugin extends ProgramPlugin tool.showComponentProvider(provider, visible); } - private void typeAdded(String type) { - provider.typeAdded(type); - getBookmarkNavigator(bookmarkMgr.getBookmarkType(type)); + private void typeAdded(ProgramChangeRecord rec) { + BookmarkType type = (BookmarkType) rec.getObject(); + 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) { scheduleUpdate(null); provider.reload(); @@ -360,9 +344,11 @@ public class BookmarkPlugin extends ProgramPlugin nav.add(bookmark.getAddress()); scheduleUpdate(bookmark.getType().getTypeString()); provider.bookmarkChanged(bookmark); + repaintMgr.update(); } - private void bookmarkAdded(Bookmark bookmark) { + private void bookmarkAdded(ProgramChangeRecord rec) { + Bookmark bookmark = (Bookmark) rec.getObject(); if (bookmark == null) { scheduleUpdate(null); provider.reload(); @@ -372,9 +358,11 @@ public class BookmarkPlugin extends ProgramPlugin nav.add(bookmark.getAddress()); // scheduleUpdate(bookmark.getType().getTypeString()); provider.bookmarkAdded(bookmark); + repaintMgr.update(); } - private void bookmarkRemoved(Bookmark bookmark) { + private void bookmarkRemoved(ProgramChangeRecord rec) { + Bookmark bookmark = (Bookmark) rec.getObject(); if (bookmark == null) { scheduleUpdate(null); provider.reload(); @@ -390,13 +378,14 @@ public class BookmarkPlugin extends ProgramPlugin } } provider.bookmarkRemoved(bookmark); + repaintMgr.update(); } @Override protected synchronized void programDeactivated(Program program) { provider.setProgram(null); navUpdater.setProgram(null); - program.removeListener(this); + program.removeListener(domainObjectListener); disposeAllBookmarkers(); bookmarkMgr = null; } @@ -413,7 +402,7 @@ public class BookmarkPlugin extends ProgramPlugin @Override protected synchronized void programActivated(Program program) { - program.addListener(this); + program.addListener(domainObjectListener); navUpdater.setProgram(program); initializeBookmarkers(); provider.setProgram(program); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java index 496e9abb57..148d2ae6e3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java @@ -16,6 +16,7 @@ package ghidra.app.plugin.core.calltree; import static ghidra.framework.model.DomainObjectEvent.*; +import static ghidra.program.util.ProgramEvent.*; import java.awt.*; import java.awt.event.*; @@ -38,7 +39,8 @@ import generic.theme.GIcon; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent; 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.preferences.Preferences; import ghidra.program.database.symbol.FunctionSymbol; @@ -54,7 +56,7 @@ import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.TaskMonitor; 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 TITLE = "Function Call Trees"; @@ -94,6 +96,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain */ private AtomicInteger recurseDepth = new AtomicInteger(); private NumberIcon recurseIcon; + private DomainObjectListener domainObjectListener = createDomainObjectListener(); public CallTreeProvider(CallTreePlugin plugin, boolean isPrimary) { super(plugin.getTool(), TITLE, plugin.getName()); @@ -852,7 +855,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain incomingTree.dispose(); outgoingTree.dispose(); if (currentProgram != null) { - currentProgram.removeListener(this); + currentProgram.removeListener(domainObjectListener); 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 // will have been null'ed out. currentProgram = plugin.getCurrentProgram(); - currentProgram.addListener(this); + currentProgram.addListener(domainObjectListener); } Function function = plugin.getFunction(location); @@ -991,7 +994,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain } currentProgram = program; - currentProgram.addListener(this); + currentProgram.addListener(domainObjectListener); doSetLocation(location); } @@ -1001,7 +1004,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain } currentProgram = program; - currentProgram.addListener(this); + currentProgram.addListener(domainObjectListener); setLocation(plugin.getCurrentLocation()); } @@ -1018,7 +1021,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain return; // not my program } - program.removeListener(this); + program.removeListener(domainObjectListener); clearState(); currentProgram = null; @@ -1044,54 +1047,34 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain return navigateIncomingToggleAction.isSelected(); } - @Override - public void domainObjectChanged(DomainObjectChangedEvent event) { - if (!isVisible()) { + private DomainObjectListener createDomainObjectListener() { + // @formatter:off + return new DomainObjectListenerBuilder(this) + .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 + } + + private void handleSymbolRenamed(ProgramChangeRecord r) { + Symbol symbol = (Symbol) r.getObject(); + if (!(symbol instanceof FunctionSymbol)) { return; } - if (isEmpty()) { - return; // nothing to update + FunctionSymbol functionSymbol = (FunctionSymbol) symbol; + Function function = (Function) functionSymbol.getObject(); + if (updateRootNodes(function)) { + return; // the entire tree will be rebuilt } - 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)) { - break; - } - - FunctionSymbol functionSymbol = (FunctionSymbol) symbol; - Function function = (Function) functionSymbol.getObject(); - if (updateRootNodes(function)) { - return; // the entire tree will be rebuilt - } - - incomingTree.runTask(new UpdateFunctionNodeTask(incomingTree, function)); - outgoingTree.runTask(new UpdateFunctionNodeTask(outgoingTree, function)); - break; - default: - } - } - } + incomingTree.runTask(new UpdateFunctionNodeTask(incomingTree, function)); + outgoingTree.runTask(new UpdateFunctionNodeTask(outgoingTree, function)); } private boolean isEmpty() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java index b3fc3b053a..356e6c283d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java @@ -24,8 +24,8 @@ import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.services.GoToService; -import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectListener; +import ghidra.framework.model.DomainObjectListenerBuilder; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; @@ -52,11 +52,12 @@ import ghidra.util.task.SwingUpdateManager; servicesRequired = { GoToService.class } ) //@formatter:on -public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectListener { +public class CommentWindowPlugin extends ProgramPlugin { private DockingAction selectAction; private CommentWindowProvider provider; private SwingUpdateManager reloadUpdateMgr; + private DomainObjectListener domainObjectListener = createDomainObjectListener(); public CommentWindowPlugin(PluginTool tool) { super(tool); @@ -76,44 +77,42 @@ public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectLi public void dispose() { reloadUpdateMgr.dispose(); if (currentProgram != null) { - currentProgram.removeListener(this); + currentProgram.removeListener(domainObjectListener); } provider.dispose(); super.dispose(); } - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { + private DomainObjectListener createDomainObjectListener() { + // @formatter:off + return new DomainObjectListenerBuilder(this) + .any(RESTORED, CODE_REMOVED).terminate(this::reload) + .with(CommentChangeRecord.class) + .each(COMMENT_CHANGED).call(this::handleCommentChanged) + .build(); + // @formatter:on + } - // reload the table if an undo/redo or clear code with options event happens (it isn't the - // same as a delete comment) - if (ev.contains(RESTORED, CODE_REMOVED)) { - reload(); - return; + private void handleCommentChanged(CommentChangeRecord ccr) { + int commentType = ccr.getCommentType(); + String oldComment = ccr.getOldComment(); + String newComment = ccr.getNewComment(); + Address commentAddress = ccr.getStart(); + + // if old comment is null then the change is an add comment so add the comment to the table + if (oldComment == null) { + provider.commentAdded(commentAddress, commentType); } - ev.forEach(COMMENT_CHANGED, r -> { - CommentChangeRecord ccr = (CommentChangeRecord) r; - int commentType = ccr.getCommentType(); - String oldComment = ccr.getOldComment(); - String newComment = ccr.getNewComment(); - Address commentAddress = ccr.getStart(); + // if the new comment is null then the change is a delete comment so remove the comment from the table + else if (newComment == null) { + provider.commentRemoved(commentAddress, commentType); + } + // otherwise, the comment is changed so repaint the table + else { + provider.getComponent().repaint(); + } - // if old comment is null then the change is an add comment so add the comment to the table - if (oldComment == null) { - provider.commentAdded(commentAddress, commentType); - } - - // if the new comment is null then the change is a delete comment so remove the comment from the table - else if (newComment == null) { - provider.commentRemoved(commentAddress, commentType); - } - // otherwise, the comment is changed so repaint the table - else { - provider.getComponent().repaint(); - } - - }); } private void reload() { @@ -126,13 +125,13 @@ public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectLi @Override protected void programActivated(Program program) { - program.addListener(this); + program.addListener(domainObjectListener); provider.programOpened(program); } @Override protected void programDeactivated(Program program) { - program.removeListener(this); + program.removeListener(domainObjectListener); provider.programClosed(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java index 654f23a057..86bf0cb77c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java @@ -29,8 +29,8 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.services.GoToService; import ghidra.app.services.ProgramTreeService; -import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectListener; +import ghidra.framework.model.DomainObjectListenerBuilder; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -59,7 +59,7 @@ import ghidra.util.task.SwingUpdateManager; eventsConsumed = { ViewChangedPluginEvent.class } ) //@formatter:on -public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListener { +public class DataWindowPlugin extends ProgramPlugin { private DockingAction selectAction; private FilterAction filterAction; @@ -68,6 +68,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe private SwingUpdateManager resetUpdateMgr; private SwingUpdateManager reloadUpdateMgr; private boolean resetTypesNeeded; + private DomainObjectListener domainObjectListener = createDomainObjectListener(); public DataWindowPlugin(PluginTool tool) { super(tool); @@ -90,35 +91,32 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe reloadUpdateMgr.dispose(); resetUpdateMgr.dispose(); if (currentProgram != null) { - currentProgram.removeListener(this); + currentProgram.removeListener(domainObjectListener); } provider.dispose(); super.dispose(); } - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.contains(RESTORED)) { - resetTypes(); - reload(); - return; - } + DomainObjectListener createDomainObjectListener() { + // @formatter:off + return new DomainObjectListenerBuilder(this) + .any(RESTORED) + .terminate(() -> resetTypes()) + .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, - DATA_TYPE_REPLACED, DATA_TYPE_SETTING_CHANGED)) { - resetTypes(); + private void codeAdded(ProgramChangeRecord rec) { + if (rec.getNewValue() instanceof Data) { + provider.dataAdded(rec.getStart()); } - - 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) { - provider.dataAdded(((ProgramChangeRecord) rec).getStart()); - } - }); } void reload() { @@ -143,7 +141,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe @Override protected void programActivated(Program program) { - program.addListener(this); + program.addListener(domainObjectListener); provider.programOpened(program); filterAction.programOpened(program); resetTypes(); @@ -151,7 +149,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe @Override protected void programDeactivated(Program program) { - program.removeListener(this); + program.removeListener(domainObjectListener); provider.programClosed(); filterAction.programClosed(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java index fcc8a91491..505709310c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java @@ -25,7 +25,8 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction; 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.PluginTool; 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.symbol.Symbol; import ghidra.program.util.ProgramChangeRecord; -import ghidra.program.util.ProgramEvent; import ghidra.util.table.SelectionNavigationAction; import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.SwingUpdateManager; @@ -49,12 +49,13 @@ import ghidra.util.task.SwingUpdateManager; eventsConsumed = { ProgramClosedPluginEvent.class } ) //@formatter:on -public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener { +public class FunctionWindowPlugin extends ProgramPlugin { private DockingAction selectAction; private DockingAction compareFunctionsAction; private FunctionWindowProvider provider; private SwingUpdateManager swingMgr; + private DomainObjectListener domainObjectListener = createDomainObjectListener(); public FunctionWindowPlugin(PluginTool tool) { super(tool); @@ -80,7 +81,7 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL @Override public void dispose() { if (currentProgram != null) { - currentProgram.removeListener(this); + currentProgram.removeListener(domainObjectListener); } swingMgr.dispose(); if (provider != null) { @@ -106,88 +107,66 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL } } - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { + private DomainObjectListener createDomainObjectListener() { + // @formatter:off + return new DomainObjectListenerBuilder(this) + .ignoreWhen(() -> !provider.isVisible()) + .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 (!provider.isVisible()) { - return; + private void functionAdded(ProgramChangeRecord rec) { + Function function = (Function) rec.getObject(); + provider.functionAdded(function); + } + + private void functionRemoved(ProgramChangeRecord rec) { + Function function = (Function) rec.getObject(); + if (function != null) { + provider.functionRemoved(function); } + } - if (ev.contains(RESTORED, MEMORY_BLOCK_MOVED, MEMORY_BLOCK_REMOVED)) { - provider.reload(); - return; + private void functionChanged(ProgramChangeRecord rec) { + Function function = (Function) rec.getObject(); + provider.update(function); + } + + private void symbolChanged(ProgramChangeRecord rec) { + Symbol sym = (Symbol) rec.getNewValue(); + Address addr = sym.getAddress(); + Function function = currentProgram.getListing().getFunctionAt(addr); + if (function != null) { + provider.update(function); } + } - 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(); - provider.functionAdded(function); - break; - case FUNCTION_REMOVED: - rec = (ProgramChangeRecord) ev.getChangeRecord(i); - function = (Function) rec.getObject(); - if (function != null) { - provider.functionRemoved(function); - } - break; - case FUNCTION_CHANGED: - rec = (ProgramChangeRecord) ev.getChangeRecord(i); - function = (Function) rec.getObject(); - provider.update(function); - break; - case SYMBOL_ADDED: - case SYMBOL_PRIMARY_STATE_CHANGED: - rec = (ProgramChangeRecord) ev.getChangeRecord(i); - Symbol sym = (Symbol) rec.getNewValue(); - Address addr = sym.getAddress(); - function = currentProgram.getListing().getFunctionAt(addr); - if (function != null) { - provider.update(function); - } - break; - case SYMBOL_RENAMED: - rec = (ProgramChangeRecord) ev.getChangeRecord(i); - sym = (Symbol) rec.getObject(); - addr = sym.getAddress(); - function = currentProgram.getListing().getFunctionAt(addr); - if (function != null) { - 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;*/ - } - } + private void symbolRenamed(ProgramChangeRecord rec) { + Symbol sym = (Symbol) rec.getObject(); + Address addr = sym.getAddress(); + Function function = currentProgram.getListing().getFunctionAt(addr); + if (function != null) { + provider.update(function); } } @Override protected void programActivated(Program program) { - program.addListener(this); + program.addListener(domainObjectListener); provider.programOpened(program); } @Override protected void programDeactivated(Program program) { - program.removeListener(this); + program.removeListener(domainObjectListener); provider.programClosed(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java index b839fef7f9..e106a7d242 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/AddressBasedGraphDisplayListener.java @@ -23,7 +23,8 @@ import java.util.concurrent.atomic.AtomicInteger; import docking.widgets.EventTrigger; import ghidra.app.events.*; 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.PluginTool; import ghidra.framework.plugintool.util.PluginEventListener; @@ -38,13 +39,14 @@ import ghidra.util.Swing; * Base class for GraphDisplay listeners whose nodes represent addresses. */ public abstract class AddressBasedGraphDisplayListener - implements GraphDisplayListener, PluginEventListener, DomainObjectListener { + implements GraphDisplayListener, PluginEventListener { protected PluginTool tool; protected GraphDisplay graphDisplay; protected Program program; private SymbolTable symbolTable; private String name; + private DomainObjectListener domainObjectListener; private static AtomicInteger instanceCount = new AtomicInteger(1); public AddressBasedGraphDisplayListener(PluginTool tool, Program program, @@ -55,7 +57,46 @@ public abstract class AddressBasedGraphDisplayListener this.graphDisplay = display; name = getClass().getSimpleName() + instanceCount.getAndAdd(1); 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 @@ -189,52 +230,10 @@ public abstract class AddressBasedGraphDisplayListener 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 public void dispose() { Swing.runLater(() -> tool.removeListenerForAllPluginEvents(this)); - program.removeListener(this); + program.removeListener(domainObjectListener); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/InstructionInfoProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/InstructionInfoProvider.java index 6f3397b4b4..58c1761f2f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/InstructionInfoProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/processors/InstructionInfoProvider.java @@ -91,6 +91,11 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain tool = null; } + @Override + public void componentHidden() { + setProgram(null); + } + /** * Define the Main panel. * @@ -130,6 +135,10 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain } void setAddress(Address address) { + if (program == null) { + return; + } + Instruction instruction = getInstruction(address); myAddr = instruction != null ? instruction.getMinAddress() : address; SleighDebugLogger debug = null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java index 58481d2de6..72862acd04 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiProgramManager.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.progmgr; +import static ghidra.framework.model.DomainObjectEvent.*; + import java.rmi.NoSuchObjectException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -41,7 +43,7 @@ import ghidra.util.task.TaskLauncher; /** * Class for tracking open programs in the tool. */ -class MultiProgramManager implements DomainObjectListener, TransactionListener { +class MultiProgramManager implements TransactionListener { private ProgramManagerPlugin plugin; 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 // and closes are all done from the Swing thread. private Map programMap = new ConcurrentHashMap<>(); + private DomainObjectListener domainObjectListener = createDomainObjectListener(); MultiProgramManager(ProgramManagerPlugin programManagerPlugin) { this.plugin = programManagerPlugin; @@ -95,7 +98,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener { fireOpenEvents(p); - p.addListener(this); + p.addListener(domainObjectListener); p.addTransactionListener(this); } else if (!oldInfo.visible && state != ProgramManager.OPEN_HIDDEN) { @@ -112,7 +115,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener { fireActivatedEvent(null); for (Program p : programMap.keySet()) { - p.removeListener(this); + p.removeListener(domainObjectListener); p.removeTransactionListener(this); fireCloseEvents(p); p.release(tool); @@ -141,7 +144,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener { else { p.removeTransactionListener(this); programMap.remove(p); - p.removeListener(this); + p.removeListener(domainObjectListener); if (info == currentInfo) { ProgramInfo newCurrent = findNextCurrent(); setCurrentProgram(newCurrent); @@ -287,13 +290,26 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener { new ProgramVisibilityChangePluginEvent(pluginName, program, isVisible)); } - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (!(ev.getSource() instanceof Program program)) { - return; - } + DomainObjectListener createDomainObjectListener() { + // @formatter:off + return new DomainObjectListenerBuilder(this) + .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); if (info != null) { info.programSavedAs(); // updates info to new domain file @@ -302,18 +318,6 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener { String name = program.getDomainFile().toString(); 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; - } - } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index 486ae35db0..05702ebfbd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.symboltree; +import static ghidra.framework.model.DomainObjectEvent.*; import static ghidra.program.util.ProgramEvent.*; import java.awt.BorderLayout; @@ -38,7 +39,8 @@ import docking.widgets.tree.tasks.GTreeBulkTask; import generic.theme.GIcon; import ghidra.app.plugin.core.symboltree.actions.*; 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.plugintool.ComponentProviderAdapter; 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. */ private List bufferedTasks = new ArrayList<>(); + private Map treeStateMap = new HashMap<>(); private SwingUpdateManager domainChangeUpdateManager = new SwingUpdateManager(1000, AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider", () -> { @@ -101,8 +104,6 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { tree.runTask(new BulkWorkTask(tree, copiedTasks)); }); - private Map treeStateMap = new HashMap<>(); - public SymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin) { super(tool, NAME, plugin.getName()); this.plugin = plugin; @@ -110,7 +111,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { setIcon(ICON); addToToolbar(); - domainObjectListener = new SymbolTreeProviderDomainObjectListener(); + domainObjectListener = createDomainObjectListener(); localClipboard = new Clipboard(NAME); @@ -425,6 +426,10 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { tree.refilterLater(); } + private void symbolChanged(Symbol symbol) { + symbolChanged(symbol, symbol.getName()); + } + private void symbolChanged(Symbol symbol, String 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 children = root.getChildren(); + for (GTreeNode node : children) { + if (node.isExpanded()) { + return false; + } + } + return true; + } + //================================================================================================== // 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 children = root.getChildren(); - for (GTreeNode node : children) { - if (node.isExpanded()) { - return false; - } - } - return true; - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java index 92e544052b..98bf948ba2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java @@ -34,7 +34,8 @@ import ghidra.app.plugin.core.symboltree.actions.*; import ghidra.app.services.BlockModelService; import ghidra.app.services.GoToService; 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.plugintool.*; 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.symbol.*; import ghidra.program.util.ProgramChangeRecord; -import ghidra.program.util.ProgramEvent; import ghidra.util.table.GhidraTable; import ghidra.util.table.SelectionNavigationAction; import ghidra.util.table.actions.MakeProgramSelectionAction; @@ -73,7 +73,7 @@ import resources.Icons; eventsConsumed = { ProgramActivatedPluginEvent.class } ) //@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 NORM_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); @@ -94,6 +94,8 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { private BlockModelService blockModelService; private SwingUpdateManager swingMgr; + private DomainObjectListener domainObjectListener = createDomainObjectListener(); + /** * 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 @@ -147,7 +149,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { refProvider = null; } if (currentProgram != null) { - currentProgram.removeListener(this); + currentProgram.removeListener(domainObjectListener); currentProgram = null; } gotoService = null; @@ -178,7 +180,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { if (oldProg != null) { inspector.setProgram(null); - oldProg.removeListener(this); + oldProg.removeListener(domainObjectListener); domainObjectWorker.clearAllJobs(); symProvider.setProgram(null, inspector); refProvider.setProgram(null, inspector); @@ -186,7 +188,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { } currentProgram = newProg; if (newProg != null) { - currentProgram.addListener(this); + currentProgram.addListener(domainObjectListener); inspector.setProgram(currentProgram); symProvider.setProgram(currentProgram, inspector); refProvider.setProgram(currentProgram, inspector); @@ -206,99 +208,85 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { refProvider.reload(); } - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (!symProvider.isVisible() && !refProvider.isVisible()) { - return; + private DomainObjectListener createDomainObjectListener() { + // @formatter:off + return new DomainObjectListenerBuilder(this) + .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 + } + + private void codeAddedRemoved(ProgramChangeRecord rec) { + if (rec.getNewValue() instanceof Data data) { + domainObjectWorker.schedule(new CodeAddedRemoveJob(currentProgram, rec.getStart())); } + } - if (ev.contains(RESTORED, MEMORY_BLOCK_ADDED, MEMORY_BLOCK_REMOVED)) { - reload(); - return; - } + private void symbolAdded(ProgramChangeRecord rec) { + // NOTE: DOCR_SYMBOL_ADDED events are never generated for reference-based + // dynamic label symbols. Reference events must be used to check for existence + // of a dynamic label symbol. + Symbol symbol = (Symbol) rec.getNewValue(); + domainObjectWorker.schedule(new SymbolAddedJob(currentProgram, symbol)); + } - int eventCnt = ev.numRecords(); - for (int i = 0; i < eventCnt; ++i) { - DomainObjectChangeRecord doRecord = ev.getChangeRecord(i); + private void symbolRemoved(ProgramChangeRecord rec) { + Address removeAddr = rec.getStart(); + Long symbolID = (Long) rec.getNewValue(); + domainObjectWorker.schedule(new SymbolRemovedJob(currentProgram, removeAddr, symbolID)); + } - 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; + private void symbolChanged(ProgramChangeRecord rec) { + Symbol symbol = (Symbol) rec.getObject(); + domainObjectWorker.schedule(new SymbolChangedJob(currentProgram, symbol)); + } - case SYMBOL_ADDED: - // NOTE: DOCR_SYMBOL_ADDED events are never generated for reference-based - // dynamic label symbols. Reference events must be used to check for existence - // of a dynamic label symbol. - Symbol symbol = (Symbol) rec.getNewValue(); - domainObjectWorker.schedule(new SymbolAddedJob(currentProgram, symbol)); - break; + private void symbolSourceChanged(ProgramChangeRecord rec) { + Symbol symbol = (Symbol) rec.getObject(); + domainObjectWorker.schedule(new SymbolSourceChangedJob(currentProgram, symbol)); + } - case SYMBOL_REMOVED: + private void symbolPrimaryStateChanged(ProgramChangeRecord rec) { + Symbol symbol = (Symbol) rec.getNewValue(); + Symbol oldPrimarySymbol = (Symbol) rec.getOldValue(); + domainObjectWorker + .schedule(new SymbolSetAsPrimaryJob(currentProgram, symbol, oldPrimarySymbol)); + } - Address removeAddr = rec.getStart(); - Long symbolID = (Long) rec.getNewValue(); - domainObjectWorker.schedule( - new SymbolRemovedJob(currentProgram, removeAddr, symbolID)); - break; + private void referenceAdded(ProgramChangeRecord rec) { + Reference ref = (Reference) rec.getObject(); + domainObjectWorker.schedule(new ReferenceAddedJob(currentProgram, ref)); + } - case SYMBOL_RENAMED: - case SYMBOL_SCOPE_CHANGED: - case SYMBOL_DATA_CHANGED: + private void referenceRemoved(ProgramChangeRecord rec) { + Reference ref = (Reference) rec.getObject(); + domainObjectWorker.schedule(new ReferenceRemovedJob(currentProgram, ref)); + } - symbol = (Symbol) rec.getObject(); - domainObjectWorker.schedule(new SymbolChangedJob(currentProgram, symbol)); - break; - - case SYMBOL_SOURCE_CHANGED: - - symbol = (Symbol) rec.getObject(); - domainObjectWorker - .schedule(new SymbolSourceChangedJob(currentProgram, symbol)); - break; - - case SYMBOL_PRIMARY_STATE_CHANGED: - - symbol = (Symbol) rec.getNewValue(); - Symbol oldPrimarySymbol = (Symbol) rec.getOldValue(); - domainObjectWorker.schedule( - new SymbolSetAsPrimaryJob(currentProgram, symbol, oldPrimarySymbol)); - break; - - case SYMBOL_ASSOCIATION_ADDED: - case SYMBOL_ASSOCIATION_REMOVED: - break; - case REFERENCE_ADDED: - - Reference ref = (Reference) rec.getObject(); - domainObjectWorker.schedule(new ReferenceAddedJob(currentProgram, ref)); - break; - - case REFERENCE_REMOVED: - - ref = (Reference) rec.getObject(); - domainObjectWorker.schedule(new ReferenceRemovedJob(currentProgram, ref)); - break; - - case EXTERNAL_ENTRY_ADDED: - case EXTERNAL_ENTRY_REMOVED: - - Address address = rec.getStart(); - domainObjectWorker - .schedule(new ExternalEntryChangedJob(currentProgram, address)); - break; - } - } - } + private void extenalEntryAddedRemoved(ProgramChangeRecord rec) { + Address address = rec.getStart(); + domainObjectWorker.schedule(new ExternalEntryChangedJob(currentProgram, address)); } Program getProgram() { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/AbstractDomainObjectListenerBuilder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/AbstractDomainObjectListenerBuilder.java new file mode 100644 index 0000000000..2c27ba19a6 --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/AbstractDomainObjectListenerBuilder.java @@ -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 The DomainObjectChangeRecord type + * @param The AbstractDomainObjectListeBuilder type (the only difference is R, the record type) + */ + +public abstract class AbstractDomainObjectListenerBuilder> { + private String name; + private BooleanSupplier ignoreCheck; + private List terminateList = new ArrayList<>(); + private List onAnyList = new ArrayList<>(); + private Map> onEachMap = + new HashMap<>(); + + private Class 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 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 the new record type + * @param 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 > B2 with( + Class 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 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 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 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 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 consumer) { + TypedRecordConsumer trc = new TypedRecordConsumer(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 biConsumer) { + TypedRecordConsumer trc = new TypedRecordConsumer(biConsumer, activeRecordType); + for (EventType eventType : eventTypeList) { + onEachMap.put(eventType, trc); + } + return self(); + } + } + + static class EventTrigger implements Consumer { + private final Consumer consumer; + private final EventType[] eventTypes; + + EventTrigger(Consumer 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 The type of record and consumer for this class. + */ + static class TypedRecordConsumer + implements BiConsumer { + private Class recordClass; + private BiConsumer consumer; + private String inceptionInformation; + + TypedRecordConsumer(Consumer consumerX, + Class recordClass) { + this((a, b) -> consumerX.accept(b), recordClass); + } + + TypedRecordConsumer(BiConsumer consumer, + Class 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 terminateList; + private List onAnyList; + private Map> 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 terminatEventList) { + this.terminateList = terminatEventList; + } + + void setOnAnyList(List onAnyList) { + this.onAnyList = onAnyList; + } + + void setOnEachMap( + Map> 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); + } + } + } + + } + +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectChangedEvent.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectChangedEvent.java index 90bddd9f4a..3a2fadf340 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectChangedEvent.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectChangedEvent.java @@ -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; + } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectListenerBuilder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectListenerBuilder.java new file mode 100644 index 0000000000..e8863c4aa6 --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectListenerBuilder.java @@ -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 + *

+ * There are three basic ways to process {@link DomainObjectChangeRecord}s within a + * {@link DomainObjectChangedEvent}. + *

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. + *

+ *

+ * new DomainObjectListenerBuilder()
+ *	.any(DomainObjectEvent.RESTORED).call(() -> refreshAll())
+ *	.build();
+ * 
+ * + *or if you need the event, you can use a consumer + * + *
 
+ * new DomainObjectListenerBuilder()
+ *	.any(DomainObjectEvent.RESTORED).call(e -> refreshAll(e))
+ *	.build();
+ * 
+ *

+ * 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)} + *

+ *

+ * new DomainObjectListenerBuilder()
+ *	.onAny(ProgramEvent.FUNCTION_CHANGED).call(() -> refreshFunctions())
+ *	.build();
+ * 
+ *or if you need the event, you can use a consumer + *
+ *
+ * new DomainObjectListenerBuilder()
+ *	.onAny(ProgramEvent.FUNCTION_CHANGED).call(e -> refreshFunctions(e))
+ *	.build();
+ * 
+ *

+ * 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. + *

+ * 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. + *

+ *

+ * 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) {
+ * 		...
+ * }
+ * 
+ * + * or if you also need the event (to get the domainObject that is the event source) + * + *
 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().
+ * 	    ...
+ * }
+ * 
+ */ + +public class DomainObjectListenerBuilder extends + AbstractDomainObjectListenerBuilder { + + /** + * 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; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramChangeRecord.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramChangeRecord.java index 6b19acc14c..ad7a82cb84 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramChangeRecord.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ProgramChangeRecord.java @@ -16,7 +16,6 @@ package ghidra.program.util; import ghidra.framework.model.DomainObjectChangeRecord; -import ghidra.framework.model.EventType; import ghidra.program.model.address.Address; /**