diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/events/CloseProgramPluginEvent.java b/Ghidra/Features/Base/src/main/java/ghidra/app/events/CloseProgramPluginEvent.java
index 36aff6b3b6..d039488aa6 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/events/CloseProgramPluginEvent.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/events/CloseProgramPluginEvent.java
@@ -18,16 +18,11 @@ package ghidra.app.events;
import java.lang.ref.WeakReference;
import ghidra.framework.plugintool.PluginEvent;
-import ghidra.framework.plugintool.ToolEventName;
import ghidra.program.model.listing.Program;
/**
- * Event for telling a tool to open a program
- *
- * This event shares a common tool-event name with the {@link OpenProgramPluginEvent}
- * so that they have a single shared tool connection.
+ * Event for telling a tool to close a program
*/
-@ToolEventName(OpenProgramPluginEvent.TOOL_EVENT_NAME) // this allows the event to be considered for tool connection
public class CloseProgramPluginEvent extends PluginEvent {
static final String NAME = "Close Program";
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/events/DualProgramLocationPluginEvent.java b/Ghidra/Features/Base/src/main/java/ghidra/app/events/DualProgramLocationPluginEvent.java
deleted file mode 100644
index 9b45a30013..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/events/DualProgramLocationPluginEvent.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.events;
-
-import ghidra.framework.plugintool.PluginEvent;
-import ghidra.framework.plugintool.ToolEventName;
-import ghidra.program.model.listing.Program;
-import ghidra.program.util.ProgramLocation;
-
-/**
- * This plugin event class provides program location information for
- * plugins that send information to two or more tools containing associated addresses.
- */
-@ToolEventName(DualProgramLocationPluginEvent.NAME) // this allows the event to be considered for tool connection
-public final class DualProgramLocationPluginEvent extends PluginEvent {
-
- /**
- * Name of this plugin event.
- */
- public static final String NAME = "DualProgramLocation";
-
- private ProgramLocation loc;
- private String programName;
-
- /**
- * Construct a new DualProgramLocationPluginEvent.
- * @param src the name of the plugin that generated this event.
- * @param loc the ProgramLocation object that contains the new location.
- * @param programName the name of the program for which the loc object refers.
- */
- public DualProgramLocationPluginEvent(String src, ProgramLocation loc, String programName) {
- super(src, NAME);
- this.loc = loc;
- this.programName = programName;
- }
-
- /**
- * Construct a new DualProgramLocationPluginEvent.
- * @param src the name of the plugin that generated this event.
- * @param loc the ProgramLocation object that contains the new location.
- * @param program the program for which the loc object refers.
- */
- public DualProgramLocationPluginEvent(String src, ProgramLocation loc, Program program) {
- super(src, NAME);
- this.loc = loc;
- this.programName = program.getName();
- }
-
- /**
- * Returns the ProgramLocation stored in this event.
- */
- public ProgramLocation getLocation() {
- return loc;
- }
-
- /**
- * Returns the Program object that the location refers to.
- */
- public String getProgramName() {
- return programName;
- }
-
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/events/OpenProgramPluginEvent.java b/Ghidra/Features/Base/src/main/java/ghidra/app/events/OpenProgramPluginEvent.java
index ad67b55586..9cbf4c14dc 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/events/OpenProgramPluginEvent.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/events/OpenProgramPluginEvent.java
@@ -18,20 +18,14 @@ package ghidra.app.events;
import java.lang.ref.WeakReference;
import ghidra.framework.plugintool.PluginEvent;
-import ghidra.framework.plugintool.ToolEventName;
import ghidra.program.model.listing.Program;
/**
* Event for telling a tool to open a program
- *
- * This event shares a common tool-event name with the {@link OpenProgramPluginEvent}
- * so that they have a single shared tool connection.
*/
-@ToolEventName(OpenProgramPluginEvent.TOOL_EVENT_NAME) // this allows the event to be considered for tool connection
public class OpenProgramPluginEvent extends PluginEvent {
static final String NAME = "Open Program";
- static final String TOOL_EVENT_NAME = "Open/Close Program";
private WeakReference programRef;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramCache.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramCache.java
index 5cee62e4f8..a10028d25e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramCache.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramCache.java
@@ -20,7 +20,7 @@ import java.util.HashMap;
import java.util.Map;
import ghidra.framework.data.DomainObjectFileListener;
-import ghidra.framework.model.DomainObject;
+import ghidra.framework.model.*;
import ghidra.program.model.listing.Program;
import ghidra.util.timer.GTimerCache;
@@ -66,6 +66,7 @@ class ProgramCache extends GTimerCache {
program.addConsumer(this);
ProgramFileListener listener = new ProgramFileListener(key);
program.addDomainFileListener(listener);
+ program.addListener(listener);
listenerMap.put(program, listener);
}
@@ -73,9 +74,10 @@ class ProgramCache extends GTimerCache {
protected void valueRemoved(ProgramLocator locator, Program program) {
// whenever programs are removed from the cache, we need to remove the cache as a consumer
// and remove the file changed listener
- program.release(this);
ProgramFileListener listener = listenerMap.remove(program);
program.removeDomainFileListener(listener);
+ program.removeListener(listener);
+ program.release(this);
}
@Override
@@ -92,9 +94,11 @@ class ProgramCache extends GTimerCache {
* DomainObjectFileListener for programs in the cache. If a program instance has its DomainFile
* changed (e.g., 'Save As' action), then the cache mapping is incorrect as it sill has the
* program instance associated with its old DomainFile. So we need to add a listener to
- * recognize when this occurs. If it does, we simply remove the entry from the cache.
+ * recognize when this occurs. If it does, we simply remove the entry from the cache. Also,
+ * we need to remove any programs from the cache if changes are made to avoid questions about
+ * who is responsible for saving changed programs that only live in the cache.
*/
- class ProgramFileListener implements DomainObjectFileListener {
+ class ProgramFileListener implements DomainObjectFileListener, DomainObjectListener {
private ProgramLocator key;
ProgramFileListener(ProgramLocator key) {
@@ -105,6 +109,10 @@ class ProgramCache extends GTimerCache {
public void domainFileChanged(DomainObject object) {
remove(key);
}
- }
+ @Override
+ public void domainObjectChanged(DomainObjectChangedEvent ev) {
+ remove(key);
+ }
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java
index 58f3a0857c..368a795e3c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramManagerPlugin.java
@@ -304,7 +304,13 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager, Opti
program = programMgr.getOpenProgram(locator);
if (program != null) {
program.addConsumer(consumer);
- programCache.put(locator, program);
+ if (!program.isChanged()) {
+ // Don't put modified programs into the cache.
+ // NOTE: This will prevent upgraded programs from being added to the cache
+ // which are already open in the tool. This could be improved if we could
+ // distinguish between upgrade and non-upgrade changes.
+ programCache.put(locator, program);
+ }
return program;
}
@@ -341,6 +347,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager, Opti
@Override
public void dispose() {
+ programCache.clear();
programMgr.dispose();
tool.clearLastEvents();
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java
index f9a5dc6523..393e932cec 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/ProgramSaveManager.java
@@ -66,8 +66,7 @@ class ProgramSaveManager {
* the user
*/
boolean canClose(Program program) {
- if (program == null ||
- (program.getDomainFile().getConsumers().size() > 1 && !tool.hasToolListeners())) {
+ if (!isOnlyToolConsumer(program)) {
return true;
}
if (acquireSaveLock(program, "Close")) {
@@ -105,9 +104,7 @@ class ProgramSaveManager {
return saveChangedPrograms(saveList);
}
finally {
- Iterator it = lockList.iterator();
- while (it.hasNext()) {
- Program p = it.next();
+ for (Program p : lockList) {
p.unlock();
}
}
@@ -378,10 +375,9 @@ class ProgramSaveManager {
"The Program is currently being modified by the following actions/tasks:\n ");
TransactionInfo t = program.getCurrentTransactionInfo();
List list = t.getOpenSubTransactions();
- Iterator it = list.iterator();
- while (it.hasNext()) {
+ for (String element : list) {
buf.append("\n ");
- buf.append(it.next());
+ buf.append(element);
}
buf.append("\n \n");
buf.append("WARNING! The above task(s) should be cancelled before attempting a " +
@@ -412,10 +408,9 @@ class ProgramSaveManager {
"The Program is currently being modified by the following actions/tasks:\n ");
TransactionInfo t = program.getCurrentTransactionInfo();
List list = t.getOpenSubTransactions();
- Iterator it = list.iterator();
- while (it.hasNext()) {
+ for (String element : list) {
buf.append("\n ");
- buf.append(it.next());
+ buf.append(element);
}
buf.append("\n \n");
buf.append(
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java
index eada70135d..157d54eb22 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptAskMethodsTest.java
@@ -130,10 +130,14 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT
public void testAskProgram_SCR8486() throws Exception {
createScript();
- Program[] container = new Program[1];
+ AtomicReference container = new AtomicReference<>();
runSwing(() -> {
try {
- container[0] = script.askProgram("Test - Pick Program");
+ Program p = script.askProgram("Test - Pick Program");
+ container.set(p);
+ if (p != null) {
+ p.release(this);
+ }
}
catch (Exception ioe) {
failWithException("Caught unexepected during askProgram()", ioe);
@@ -146,7 +150,7 @@ public class GhidraScriptAskMethodsTest extends AbstractGhidraHeadedIntegrationT
runSwing(() -> okButton.doClick());
// this test will fail if we encountered an exception
- assertNull(container[0]);
+ assertNull(container.get());
runSwing(() -> dtd.close());
}