diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java index 3a613f554d..61864bd7f2 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java @@ -768,7 +768,6 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter @Override public void breakpointsRemoved(Collection clb) { Swing.runIfSwingOrRunLater(() -> { - Msg.debug(this, "LBs removed: " + clb); breakpointTableModel.deleteAllItems(clb); contextChanged(); }); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java index 2b774d3130..2636c7bdff 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/LogicalBreakpointRow.java @@ -34,6 +34,11 @@ public class LogicalBreakpointRow { this.lb = lb; } + @Override + public String toString() { + return ""; + } + public LogicalBreakpoint getLogicalBreakpoint() { return lb; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java index 8469ae9020..b31aaed71d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServicePlugin.java @@ -52,27 +52,26 @@ import ghidra.util.Swing; import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.datastruct.ListenerSet; -@PluginInfo( // - shortDescription = "Debugger logical breakpoints service plugin", // - description = "Aggregates breakpoints from open programs and live traces", // - category = PluginCategoryNames.DEBUGGER, // - packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.RELEASED, // - eventsConsumed = { // - ProgramOpenedPluginEvent.class, // - ProgramClosedPluginEvent.class, // - TraceOpenedPluginEvent.class, // - TraceClosedPluginEvent.class, // - }, // - servicesRequired = { // - DebuggerTraceManagerService.class, // - DebuggerModelService.class, // - DebuggerStaticMappingService.class, // - }, // - servicesProvided = { // - DebuggerLogicalBreakpointService.class, // - } // -) +@PluginInfo( + shortDescription = "Debugger logical breakpoints service plugin", + description = "Aggregates breakpoints from open programs and live traces", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + eventsConsumed = { + ProgramOpenedPluginEvent.class, + ProgramClosedPluginEvent.class, + TraceOpenedPluginEvent.class, + TraceClosedPluginEvent.class, + }, + servicesRequired = { + DebuggerTraceManagerService.class, + DebuggerModelService.class, + DebuggerStaticMappingService.class, + }, + servicesProvided = { + DebuggerLogicalBreakpointService.class, + }) public class DebuggerLogicalBreakpointServicePlugin extends Plugin implements DebuggerLogicalBreakpointService { @@ -154,8 +153,9 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin protected class TrackMappingsListener implements DebuggerStaticMappingChangeListener { @Override public void mappingsChanged(Set affectedTraces, Set affectedPrograms) { - Msg.debug(this, "Mappings changed: " + affectedTraces + "," + affectedPrograms); + // Msg.debug(this, "Mappings changed: " + affectedTraces + "," + affectedPrograms); synchronized (lock) { + // Msg.debug(this, "Processing map change"); Set additionalTraces = new HashSet<>(affectedTraces); Set additionalPrograms = new HashSet<>(affectedPrograms); try (RemoveCollector r = new RemoveCollector(changeListeners.fire)) { @@ -209,20 +209,21 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } private void objectRestored() { - Msg.debug(this, "Restored: " + info.trace); + // Msg.debug(this, "Restored: " + info.trace); synchronized (lock) { info.reloadBreakpoints(); } } private void breakpointAdded(TraceBreakpoint breakpoint) { - Msg.debug(this, "Breakpoint added: " + breakpoint); + // Msg.debug(this, "Breakpoint added: " + breakpoint); if (!breakpoint.getLifespan().contains(info.recorder.getSnap())) { // NOTE: User/script probably added historical breakpoint return; } try (AddCollector c = new AddCollector(changeListeners.fire)) { synchronized (lock) { + // Msg.debug(this, "Processing breakpoint add"); info.trackTraceBreakpoint(breakpoint, c, false); } } @@ -241,7 +242,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull, TraceBreakpoint breakpoint, Range oldSpan, Range newSpan) { - Msg.debug(this, "Breakpoint span: " + breakpoint); + // Msg.debug(this, "Breakpoint span: " + breakpoint); // NOTE: User/script probably modified historical breakpoint boolean isInOld = oldSpan.contains(info.recorder.getSnap()); boolean isInNew = newSpan.contains(info.recorder.getSnap()); @@ -265,7 +266,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } private void breakpointDeleted(TraceBreakpoint breakpoint) { - Msg.debug(this, "Breakpoint deleted: " + breakpoint); + // Msg.debug(this, "Breakpoint deleted: " + breakpoint); if (!breakpoint.getLifespan().contains(info.recorder.getSnap())) { // NOTE: User/script probably removed historical breakpoint assert false; @@ -295,7 +296,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } private void objectRestored() { - Msg.debug(this, "Restored: " + info.program); + // Msg.debug(this, "Restored: " + info.program); synchronized (lock) { info.reloadBreakpoints(); // NOTE: logical breakpoints should know which traces are affected @@ -315,8 +316,10 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } private void breakpointBookmarkAdded(Bookmark bookmark) { + // Msg.debug(this, "Breakpoint bookmark added: " + bookmark); try (AddCollector c = new AddCollector(changeListeners.fire)) { synchronized (lock) { + // Msg.debug(this, "Processing breakpoint bookmark add"); info.trackProgramBreakpoint(bookmark, c); } } @@ -821,7 +824,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin } private void programOpened(Program program) { - Msg.debug(this, "Opened Program: " + program); + // Msg.debug(this, "Opened Program: " + program); synchronized (lock) { if (program instanceof TraceProgramView) { // TODO: Not a good idea for user/script, but not prohibited diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java index 6a622a2a25..95297fb7db 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LoneLogicalBreakpoint.java @@ -44,6 +44,11 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal { this.justThisTrace = Set.of(recorder.getTrace()); } + @Override + public String toString() { + return String.format("<%s trace=%s>", getClass().getSimpleName(), breaks); + } + @Override public boolean isEmpty() { return breaks.isEmpty(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java index 9f45c67d1e..4af14865de 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/MappedLogicalBreakpoint.java @@ -51,7 +51,7 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal { @Override public String toString() { - return String.format("", progBreak, + return String.format("<%s prog=%s, traces=%s>", getClass().getSimpleName(), progBreak, traceBreaks.values()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java index 46eda1ff07..7ca0187453 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java @@ -191,7 +191,7 @@ public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder { Collection kinds) { for (TargetBreakpointLocation bl : bpts) { String path = PathUtils.toString(bl.getPath()); - recorder.parTx.execute("Breakpoint " + path + " toggled", () -> { + recorder.parTx.execute("Breakpoint " + path + " changed", () -> { TraceBreakpoint traceBpt = recorder.getTraceBreakpoint(bl); if (traceBpt == null) { Msg.warn(this, "Cannot find toggled trace breakpoint for " + path); @@ -210,7 +210,7 @@ public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder { spec.getLocations().thenAccept(bpts -> { doBreakpointSpecChanged(snap, bpts, enabled, kinds); }).exceptionally(ex -> { - Msg.error(this, "Error recording toggled breakpoint spec: " + spec.getJoinedPath("."), + Msg.error(this, "Error recording changed breakpoint spec: " + spec.getJoinedPath("."), ex); return null; }); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java index 91e384e30c..5b286c9989 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java @@ -502,7 +502,6 @@ public class DefaultTraceRecorder implements TraceRecorder { return objectManager.getEventListener(); } - @Override public ListenerSet getListeners() { return objectManager.getListeners(); } @@ -564,4 +563,8 @@ public class DefaultTraceRecorder implements TraceRecorder { return true; } + @Override + public CompletableFuture flushTransactions() { + return parTx.flush(); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java index 14545c6270..e99ce6447d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/PermanentTransactionExecutor.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.service.model; import java.util.HashMap; import java.util.concurrent.*; +import java.util.stream.Stream; import org.apache.commons.lang3.concurrent.BasicThreadFactory; @@ -74,4 +75,12 @@ public class PermanentTransactionExecutor { return null; }); } + + public CompletableFuture flush() { + Runnable nop = () -> { + }; + return CompletableFuture.allOf(Stream.of(threads) + .map(t -> CompletableFuture.runAsync(nop, t)) + .toArray(CompletableFuture[]::new)); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderComposedRegisterSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderComposedRegisterSet.java index 821b8b1269..c1a2e72736 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderComposedRegisterSet.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/RecorderComposedRegisterSet.java @@ -19,7 +19,6 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; -import ghidra.app.services.TraceRecorder; import ghidra.async.AsyncLazyMap; import ghidra.async.AsyncLazyMap.KeyedFuture; import ghidra.async.AsyncUtils; @@ -32,7 +31,7 @@ import ghidra.util.TriConsumer; public class RecorderComposedRegisterSet { - private TraceRecorder recorder; + private DefaultTraceRecorder recorder; protected final TriConsumer listenerRegAccChanged = this::registerAccessibilityChanged; @@ -61,7 +60,7 @@ public class RecorderComposedRegisterSet { }); } - public RecorderComposedRegisterSet(TraceRecorder recorder) { + public RecorderComposedRegisterSet(DefaultTraceRecorder recorder) { this.recorder = recorder; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java index 8ee12f0c3b..3f695ff998 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/DebuggerStaticMappingServicePlugin.java @@ -457,7 +457,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin } private void staticMappingAdded(TraceStaticMapping mapping) { - Msg.debug(this, "Trace Mapping added: " + mapping); + // Msg.debug(this, "Trace Mapping added: " + mapping); synchronized (lock) { MappingEntry me = new MappingEntry(mapping); putOutboundAndInboundEntries(me); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java index 2eed943eb1..d560555f43 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java @@ -40,7 +40,6 @@ import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceTimeManager; -import ghidra.util.datastruct.ListenerSet; import ghidra.util.task.TaskMonitor; /** @@ -480,5 +479,16 @@ public interface TraceRecorder { @Internal TraceEventListener getListenerForRecord(); - ListenerSet getListeners(); + /** + * Wait for pending transactions finish execution. + * + *

+ * The returned future will complete when queued transactions have been executed. There are no + * guarantees regarding transactions submitted after this future is returned. Furthermore, it + * may still be necessary to wait for the trace to finish invoking its domain object change + * listeners. + * + * @return the future which completes when pending transactions finish execution. + */ + CompletableFuture flushTransactions(); } diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java index 959367c1d2..18b1923a3b 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java @@ -36,6 +36,7 @@ import ghidra.app.services.*; import ghidra.dbg.model.TestDebuggerModelBuilder; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpecContainer; +import ghidra.dbg.target.TargetTogglable; import ghidra.dbg.testutil.DebuggerModelTestUtils; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; @@ -44,6 +45,7 @@ import ghidra.test.ToyProgramBuilder; import ghidra.trace.model.DefaultTraceLocation; import ghidra.trace.model.Trace; import ghidra.trace.model.breakpoint.TraceBreakpoint; +import ghidra.util.Msg; import ghidra.util.database.UndoableTransaction; import ghidra.util.task.TaskMonitor; import help.screenshot.GhidraScreenShotGenerator; @@ -86,6 +88,17 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera @After public void tearDownMine() { + Msg.debug(this, "Tearing down"); + Msg.debug(this, "Service breakpoints:"); + for (LogicalBreakpoint lb : breakpointService.getAllBreakpoints()) { + Msg.debug(this, " bp: " + lb); + } + DebuggerBreakpointsProvider provider = + waitForComponentProvider(DebuggerBreakpointsProvider.class); + Msg.debug(this, "Provider breakpoints:"); + for (LogicalBreakpointRow row : provider.breakpointTableModel.getModelData()) { + Msg.debug(this, " bp: " + row.getLogicalBreakpoint()); + } if (program != null) { program.release(this); } @@ -112,9 +125,9 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera traceManager.openTrace(trace1); traceManager.openTrace(trace3); - mb.testProcess1.addRegion("echo:.text", mb.rng(0x00400000, 0x0040fff), "rx"); - mb.testProcess1.addRegion("echo:.data", mb.rng(0x00600000, 0x0060fff), "rw"); - mb.testProcess3.addRegion("echo:.text", mb.rng(0x7fac0000, 0x7facfff), "rx"); + mb.testProcess1.addRegion("echo:.text", mb.rng(0x00400000, 0x00400fff), "rx"); + mb.testProcess1.addRegion("echo:.data", mb.rng(0x00600000, 0x00600fff), "rw"); + mb.testProcess3.addRegion("echo:.text", mb.rng(0x7fac0000, 0x7fac0fff), "rx"); try (UndoableTransaction tid = UndoableTransaction.start(trace1, "Add mapping", true)) { mappingService.addMapping( @@ -137,14 +150,13 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera waitFor(() -> Unique.assertAtMostOne(recorder3.collectBreakpointContainers(null)), "No container"); waitOn(bc3.placeBreakpoint(mb.addr(0x7fac1234), Set.of(TargetBreakpointKind.SW_EXECUTE))); + TargetTogglable bp3 = (TargetTogglable) waitForValue( + () -> Unique.assertAtMostOne(bc3.getCachedElements().values())); + waitOn(bp3.disable()); TraceBreakpoint bpt = waitForValue(() -> Unique.assertAtMostOne( trace3.getBreakpointManager() .getBreakpointsAt(recorder3.getSnap(), addr(trace3, 0x7fac1234)))); - try (UndoableTransaction tid = - UndoableTransaction.start(trace3, "Disable breakpoint", true)) { - bpt.setEnabled(false); - } try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint", true)) { program.getBookmarkManager() @@ -156,9 +168,20 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera } waitForPass(() -> { - assertEquals(3, breakpointService.getAllBreakpoints().size()); + Set allBreakpoints = breakpointService.getAllBreakpoints(); + assertEquals(3, allBreakpoints.size()); + }); + waitForPass(() -> { assertFalse(bpt.isEnabled()); }); + /** + * TODO: Might be necessary to debounce and wait for service callbacks to settle. Sometimes, + * there are 3 for just a moment, and then additional callbacks mess things up. + */ + waitForPass(() -> { + assertEquals(3, provider.breakpointTable.getRowCount()); + assertEquals(3, provider.locationTable.getRowCount()); + }); captureIsolatedProvider(provider, 600, 600); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java index b71f895264..aa1c402398 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java @@ -110,6 +110,7 @@ public class DBTraceBreakpoint } } enabled = (flagsByte & ENABLED_MASK) != 0; + // Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because doFresh"); } @Override @@ -154,6 +155,7 @@ public class DBTraceBreakpoint this.comment = comment; update(PATH_COLUMN, NAME_COLUMN, THREADS_COLUMN, FLAGS_COLUMN, COMMENT_COLUMN); this.enabled = enabled; + // Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because set"); } public void set(String path, String name, long[] threadKeys, @@ -342,11 +344,15 @@ public class DBTraceBreakpoint this.kinds.clear(); this.kinds.addAll(kinds); this.enabled = enabled; + // Msg.debug(this, + // "trace: breakpoint " + this + " enabled=" + enabled + ", because doSetFlags"); update(FLAGS_COLUMN); } protected void doSetEnabled(boolean enabled) { this.enabled = enabled; + // Msg.debug(this, + // "trace: breakpoint " + this + " enabled=" + enabled + ", because doSetEnabled"); if (enabled) { flagsByte |= ENABLED_MASK; } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java index 40d30b7630..e017949ed6 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.util.Msg; /** * A table model where the columns are enumerated, and the rows are wrappers on the objects being @@ -44,11 +45,16 @@ public class RowWrappedEnumeratedColumnTableModel & Enumerated this.wrapper = wrapper; } - protected synchronized R rowFor(T t) { - return map.computeIfAbsent(keyFunc.apply(t), k -> wrapper.apply(t)); + protected synchronized R addRowFor(T t) { + R row = wrapper.apply(t); + R exists = map.put(keyFunc.apply(t), row); + if (exists != null) { + Msg.warn(this, "Replaced existing row! row=" + exists); + } + return row; } - protected synchronized R delFor(T t) { + protected synchronized R delRowFor(T t) { return delKey(keyFunc.apply(t)); } @@ -56,50 +62,75 @@ public class RowWrappedEnumeratedColumnTableModel & Enumerated return map.remove(k); } - protected synchronized List rowsFor(Stream s) { - return s.map(this::rowFor).collect(Collectors.toList()); + protected synchronized List addRowsFor(Stream s) { + return s.map(this::addRowFor).collect(Collectors.toList()); } - protected synchronized List rowsFor(Collection c) { - return rowsFor(c.stream()); + protected synchronized List addRowsFor(Collection c) { + return addRowsFor(c.stream()); } public synchronized R getRow(T t) { return map.get(keyFunc.apply(t)); } + protected synchronized List getRows(Stream s) { + return s.map(this::getRow).filter(r -> r != null).collect(Collectors.toList()); + } + + protected synchronized List getRows(Collection c) { + return getRows(c.stream()); + } + public synchronized void addItem(T t) { if (map.containsKey(keyFunc.apply(t))) { return; } - add(rowFor(t)); + add(addRowFor(t)); } public synchronized void addAllItems(Collection c) { - Stream s = c.stream().filter(t -> !map.containsKey(keyFunc.apply(t))); - addAll(rowsFor(s)); + Stream s = c.stream().filter(t -> { + K k = keyFunc.apply(t); + if (map.containsKey(k)) { + return false; + } + return true; + }); + addAll(addRowsFor(s)); } public void updateItem(T t) { - notifyUpdated(rowFor(t)); + R row = getRow(t); + if (row == null) { + return; + } + notifyUpdated(row); } public void updateAllItems(Collection c) { - notifyUpdatedWith(rowsFor(c)::contains); + notifyUpdatedWith(getRows(c)::contains); } public void deleteItem(T t) { - delete(delFor(t)); + R row = delRowFor(t); + if (row == null) { + return; + } + delete(row); } public R deleteKey(K k) { R r = delKey(k); + if (r == null) { + return null; + } delete(r); return r; } public synchronized void deleteAllItems(Collection c) { - deleteWith(rowsFor(c)::contains); + deleteWith(getRows(c)::contains); map.keySet().removeAll(c.stream().map(keyFunc).collect(Collectors.toList())); }