diff --git a/Ghidra/Features/Base/ghidra_scripts/AskValuesExampleScript.java b/Ghidra/Features/Base/ghidra_scripts/AskValuesExampleScript.java
index 8e540e7e5b..ab90f04894 100644
--- a/Ghidra/Features/Base/ghidra_scripts/AskValuesExampleScript.java
+++ b/Ghidra/Features/Base/ghidra_scripts/AskValuesExampleScript.java
@@ -17,7 +17,6 @@
// @category Examples
import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap;
-import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.util.MessageType;
@@ -28,20 +27,12 @@ public class AskValuesExampleScript extends GhidraScript {
GhidraValuesMap values = new GhidraValuesMap();
values.defineString("Name");
- values.defineAddress("Address", currentProgram);
values.defineInt("Count");
values.defineInt("Max Results", 100);
values.defineChoice("Priority", "Low", "Low", "Medium", "High");
-
- // When asking for a program, you must supply a consumer that you will use
- // to release the program. Since programs share open instances, Ghidra uses
- // consumers to keep track of these uses. Scripts can just add themselves
- // as the consumer (The askProgram() method does this for you). It is
- // important to release it when you are done with. Optionally, you can also
- // provide a tool in which case the program will also be opened in the tool (and the
- // tool would then also add itself as a consumer). Otherwise, the program will not
- // show up in the tool and when you release the consumer, it will be closed.
- values.defineProgram("Other Program", this, state.getTool());
+ values.defineProgram("Other Program");
+ values.defineProjectFile("Project File");
+ values.defineProjectFolder("Project Folder");
// Optional validator that can be set to validate values before the dialog returns. It
// is called when the "Ok" button is pushed and must return true before the dialog exits.
@@ -70,14 +61,23 @@ public class AskValuesExampleScript extends GhidraScript {
//dialog. The values map returned may or may not be the same instance as the one passed in.
String name = values.getString("Name");
- Address address = values.getAddress("Address");
int age = values.getInt("Count");
int max = values.getInt("Max Results");
String priority = values.getChoice("Priority");
- Program program = values.getProgram("Other Program");
+
+ // When asking for a program, you must supply a consumer that you will use
+ // to release the program. Since programs share open instances, Ghidra uses
+ // consumers to keep track of these uses. Scripts can just add themselves
+ // as the consumer (The askProgram() method does this for you). It is
+ // important to release it when you are done with it. Optionally, you can also
+ // provide a tool in which case the program will also be opened in the tool (and the
+ // tool would then also add itself as a consumer). Otherwise, the program will not
+ // show up in the tool and when you release the consumer, it will be closed.
+ // NOTE: if you call getProgram() more than once, the consumer will be added multiple times
+ // and you must release it multiple times
+ Program program = values.getProgram("Other Program", this, state.getTool());
println("Name = " + name);
- println("Address = " + address);
println("Count = " + age);
println("Max Results = " + max);
println("Priority = " + priority);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java
index 45ade2409d..df2004606b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java
@@ -2360,6 +2360,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
public GhidraValuesMap askValues(String title, String optionalMessage, GhidraValuesMap values)
throws CancelledException {
+ values.setTaskMonitor(monitor);
for (AbstractValue> value : values.getValues()) {
String key = join(title, value.getName());
loadAskValue(value.getValue(), s -> value.setAsText(s), key);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java
index 239d44fc7a..867c496dfb 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java
@@ -39,6 +39,7 @@ import ghidra.framework.*;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.*;
+import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*;
import ghidra.framework.project.DefaultProject;
import ghidra.framework.project.DefaultProjectManager;
@@ -424,6 +425,7 @@ public class HeadlessAnalyzer {
if (locator.getProjectDir().exists()) {
project = openProject(locator);
+ AppInfo.setActiveProject(project);
}
else {
if (options.runScriptsNoImport) {
@@ -440,6 +442,7 @@ public class HeadlessAnalyzer {
Msg.info(this, "Creating " + (options.deleteProject ? "temporary " : "") +
"project: " + locator);
project = getProjectManager().createProject(locator, null, false);
+ AppInfo.setActiveProject(project);
}
try {
@@ -457,6 +460,7 @@ public class HeadlessAnalyzer {
}
finally {
project.close();
+ AppInfo.setActiveProject(null);
if (!options.runScriptsNoImport && options.deleteProject) {
FileUtilities.deleteDir(locator.getProjectDir());
locator.getMarkerFile().delete();
@@ -1546,8 +1550,7 @@ public class HeadlessAnalyzer {
// Analyze the primary program, and determine if we should save.
// TODO: Analyze non-primary programs (GP-2965).
- boolean doSave =
- analyzeProgram(fsrl.toString(), primaryProgram) && !options.readOnly;
+ boolean doSave = analyzeProgram(fsrl.toString(), primaryProgram) && !options.readOnly;
// The act of marking the program as temporary by a script will signal
// us to discard any changes
@@ -1706,7 +1709,7 @@ public class HeadlessAnalyzer {
}
}
}
-
+
private boolean processAsFS(FSRL fsrl, String folderPath, int depth) throws CancelledException {
try (FileSystemRef fsRef = fsService.probeFileForFilesystem(fsrl, TaskMonitor.DUMMY,
FileSystemProbeConflictResolver.CHOOSEFIRST)) {
@@ -1746,7 +1749,6 @@ public class HeadlessAnalyzer {
storage.clear();
-
if (inputDirFiles != null && !inputDirFiles.isEmpty()) {
Msg.info(this, "REPORT: Processing input files: ");
Msg.info(this, " project: " + project.getProjectLocator());
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectBrowserPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/AbstractProjectBrowserPanel.java
similarity index 52%
rename from Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectBrowserPanel.java
rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/values/AbstractProjectBrowserPanel.java
index 4975c71919..47e71fe92f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectBrowserPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/AbstractProjectBrowserPanel.java
@@ -16,102 +16,91 @@
package ghidra.features.base.values;
import java.awt.BorderLayout;
+import java.util.Objects;
import javax.swing.*;
import docking.widgets.button.BrowseButton;
-import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.*;
import ghidra.framework.store.FileSystem;
/**
- * Component used by Values that use the DataTreeDialog for picking DomainFiles and DomainFolders
+ * Base class for either project file chooser or project folder chooser
*/
-class ProjectBrowserPanel extends JPanel {
- private JTextField textField;
+abstract class AbstractProjectBrowserPanel extends JPanel {
+ protected Project project;
+ protected JTextField textField;
private JButton browseButton;
- private boolean selectFolders;
+ private DomainFolder startFolder;
+ private int type;
+ protected DomainFileFilter filter = null;
- ProjectBrowserPanel(String name, boolean selectFolders) {
+ AbstractProjectBrowserPanel(int type, Project project, String name, String startPath) {
super(new BorderLayout());
- this.selectFolders = selectFolders;
+ this.type = type;
+ this.project = Objects.requireNonNull(project);
+ this.startFolder = parseDomainFolder(project, startPath);
setName(name);
textField = new JTextField(20);
browseButton = new BrowseButton();
- browseButton.addActionListener(e -> showDomainFileChooser());
+ browseButton.addActionListener(e -> showProjectChooser());
add(textField, BorderLayout.CENTER);
add(browseButton, BorderLayout.EAST);
}
- void setDomainFile(DomainFile value) {
- String text = value == null ? "" : value.getPathname();
- textField.setText(text);
- }
+ private void showProjectChooser() {
+ DataTreeDialog dialog =
+ new DataTreeDialog(null, "Choose " + getName(), type, filter, project);
- void setDomainFolder(DomainFolder value) {
- String text = value == null ? "" : value.getPathname();
- textField.setText(text);
- }
+ if (startFolder != null) {
+ dialog.selectFolder(startFolder);
+ }
+ intializeCurrentValue(dialog);
- private void showDomainFileChooser() {
- DataTreeDialog dialog = new DataTreeDialog(null, "Choose " + getName(),
- selectFolders ? DataTreeDialog.CHOOSE_FOLDER : DataTreeDialog.OPEN);
dialog.show();
+
if (dialog.wasCancelled()) {
return;
}
- String text = selectFolders ? dialog.getDomainFolder().getPathname()
- : dialog.getDomainFile().getPathname();
+ String text = getSelectedPath(dialog);
textField.setText(text);
dialog.dispose();
}
- DomainFile getDomainFile() {
- String text = textField.getText().trim();
- if (text.isBlank()) {
- return null;
- }
- return parseDomainFile(text);
- }
+ protected abstract String getSelectedPath(DataTreeDialog dialog);
+
+ protected abstract void intializeCurrentValue(DataTreeDialog dialog);
String getText() {
return textField.getText().trim();
}
- DomainFolder getDomainFolder() {
- String text = textField.getText().trim();
- if (text.isBlank()) {
- return parseDomainFolder("/");
- }
- return parseDomainFolder(text);
- }
-
- static DomainFile parseDomainFile(String val) {
+ static DomainFile parseDomainFile(Project project, String val) {
// Add the slash to make it an absolute path
if (!val.isEmpty() && val.charAt(0) != FileSystem.SEPARATOR_CHAR) {
val = FileSystem.SEPARATOR_CHAR + val;
}
- Project activeProject = AppInfo.getActiveProject();
- DomainFile df = activeProject.getProjectData().getFile(val);
+ DomainFile df = project.getProjectData().getFile(val);
if (df != null) {
return df;
}
return null;
}
- static DomainFolder parseDomainFolder(String path) {
+ static DomainFolder parseDomainFolder(Project project, String path) {
+ if (path == null) {
+ return null;
+ }
path = path.trim();
// Add the slash to make it an absolute path
if (path.isEmpty() || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
path = FileSystem.SEPARATOR_CHAR + path;
}
- Project activeProject = AppInfo.getActiveProject();
- DomainFolder df = activeProject.getProjectData().getFolder(path);
+ DomainFolder df = project.getProjectData().getFolder(path);
if (df != null) {
return df;
}
return null;
}
-
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/GhidraValuesMap.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/GhidraValuesMap.java
index 6144698b86..b35d0c5192 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/GhidraValuesMap.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/GhidraValuesMap.java
@@ -15,6 +15,8 @@
*/
package ghidra.features.base.values;
+import java.io.IOException;
+
import docking.Tool;
import docking.widgets.values.GValuesMap;
import ghidra.framework.model.DomainFile;
@@ -23,12 +25,24 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.listing.Program;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.VersionException;
+import ghidra.util.task.TaskMonitor;
/**
* Extends GValuesMap to add Ghidra specific types such as Address and Program
*/
public class GhidraValuesMap extends GValuesMap {
+ private TaskMonitor monitor = TaskMonitor.DUMMY;
+ /**
+ * Sets a task monitor to be used when opening programs. Otherwise, {@link TaskMonitor#DUMMY} is
+ * used.
+ * @param monitor the TaskMonitor to use for opening programs
+ */
+ public void setTaskMonitor(TaskMonitor monitor) {
+ this.monitor = TaskMonitor.dummyIfNull(monitor);
+ }
//==================================================================================================
// Define Value Methods
//==================================================================================================
@@ -88,36 +102,23 @@ public class GhidraValuesMap extends GValuesMap {
}
/**
- * Defines a value of type Program. This method opens programs using the given
- * consumer and must be properly released when it is no longer needed. This is true
- * even if the program is also opened in the tool.
+ * Defines a value of type Program file.
* @param name the name for this value
- * @param consumer the consumer to be used to open the program
- * @param tool if non-null, the program will also be opened in the given tool
- * @return the user-selected Program if a program was
- * not selected or null. NOTE: It is very important that the program instance
- * returned by this method ALWAYS be properly released from the consumer when no
- * longer needed (i.e., {@code program.release(consumer) } - failure to
- * properly release the program may result in improper project disposal. If the program was
- * also opened in the tool, the tool will be a second consumer responsible for its
- * own release.
+ * @return the new ProgramFileValue defined
*/
- public ProgramValue defineProgram(String name, Object consumer, Tool tool) {
- return defineProgram(name, null, consumer, tool);
+ public ProgramFileValue defineProgram(String name) {
+ return defineProgram(name, null);
}
/**
- * Defines a value of type Program.
+ * Defines a value of type Program file.
* @param name the name for this value
- * @param defaultValue the initial value
- * @param consumer the consumer to be used to open the program
- * @param tool if non-null, the program will also be opened in the given tool
- * @return the new ProgramValue that was defined
+ * @param startPath the starting folder to display when picking programs from the chooser
+ * @return the new ProgramFileValue that was defined
*/
- public ProgramValue defineProgram(String name, Program defaultValue, Object consumer,
- Tool tool) {
+ public ProgramFileValue defineProgram(String name, String startPath) {
checkDup(name);
- ProgramValue value = new ProgramValue(name, defaultValue, consumer, tool);
+ ProgramFileValue value = new ProgramFileValue(name, startPath);
valuesMap.put(name, value);
return value;
}
@@ -134,12 +135,12 @@ public class GhidraValuesMap extends GValuesMap {
/**
* Defines a value of type DomainFile (files in a Ghidra project).
* @param name the name for this value
- * @param defaultValue the initial value
+ * @param startingPath the initial folder path for the chooser widget
* @return the new ProjectFileValue that was defined
*/
- public ProjectFileValue defineProjectFile(String name, DomainFile defaultValue) {
+ public ProjectFileValue defineProjectFile(String name, String startingPath) {
checkDup(name);
- ProjectFileValue value = new ProjectFileValue(name, defaultValue);
+ ProjectFileValue value = new ProjectFileValue(name, startingPath);
valuesMap.put(name, value);
return value;
}
@@ -156,12 +157,12 @@ public class GhidraValuesMap extends GValuesMap {
/**
* Defines a value of type DomainFolder (files in a Ghidra project).
* @param name the name for this value
- * @param defaultValue the initial value (can be null)
+ * @param defaultValuePath the path for the initial value (can be null)
* @return the new ProjectFolderValue that was defined
*/
- public ProjectFolderValue defineProjectFolder(String name, DomainFolder defaultValue) {
+ public ProjectFolderValue defineProjectFolder(String name, String defaultValuePath) {
checkDup(name);
- ProjectFolderValue value = new ProjectFolderValue(name, defaultValue);
+ ProjectFolderValue value = new ProjectFolderValue(name, defaultValuePath);
valuesMap.put(name, value);
return value;
}
@@ -192,14 +193,33 @@ public class GhidraValuesMap extends GValuesMap {
}
/**
- * Gets the {@link Program} value for the given name.
- * @param name the name of a previously defined project folder value
+ * Gets (opens) the {@link Program} value for the given name. If the program is already open,
+ * then the consumer will be added to the program. The caller of this method is responsible
+ * for calling {@link Program#release(Object)} with the same consumer when it is done using this
+ * program. Program are only closed after all consumers are released. If multiple calls
+ * are made to this method, then the consumer will be added multiple times and must be released
+ * multiple times.
+ *
+ * The consumer can be any object, but since the consumer's purpose is to keep the program open
+ * while some object is using it, the object itself is typically passed in as
+ * the consumer. For example, when used in a script, passing in the java keyword "this" as the
+ * consumer will make the script itself the consumer.
+ *
+ * @param name the name of a previously defined program value
+ * @param consumer the consumer to be used to open the program
+ * @param tool if non-null, the program will also be opened in the given tool. Note: the
+ * program will only be added to the tool once even if this method is called multiple times.
* @return the project folder value
+ * @throws VersionException if the Program being opened is an older version than the
+ * current Ghidra Program version.
+ * @throws IOException if there is an error accessing the Program's DomainObject
+ * @throws CancelledException if the operation is cancelled
* @throws IllegalArgumentException if the name hasn't been defined as a project folder type
*/
- public Program getProgram(String name) {
- ProgramValue programValue = getValue(name, ProgramValue.class, "Program");
- return programValue.getValue();
+ public Program getProgram(String name, Object consumer, Tool tool)
+ throws VersionException, IOException, CancelledException {
+ ProgramFileValue programFileValue = getValue(name, ProgramFileValue.class, "Program");
+ return programFileValue.openProgram(consumer, tool, monitor);
}
/**
@@ -258,8 +278,8 @@ public class GhidraValuesMap extends GValuesMap {
* @throws IllegalArgumentException if the name hasn't been defined as a Program type
*/
public void setProgram(String name, Program program) {
- ProgramValue programValue = getValue(name, ProgramValue.class, "Program");
- programValue.setValue(program);
+ ProgramFileValue programFileValue = getValue(name, ProgramFileValue.class, "Program");
+ programFileValue.setValue(program == null ? null : program.getDomainFile());
}
/**
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/LanguageValue.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/LanguageValue.java
index df84b283b0..cb1a337938 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/LanguageValue.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/LanguageValue.java
@@ -91,33 +91,33 @@ public class LanguageValue extends AbstractValue {
/**
* Parses a LanguageCompilerSpecPair from a string.
*
- * @param val The string to parse.
+ * @param languageString The string to parse.
* @return The LanguageCompilerSpecPair parsed from a string or null if the string does
* not parse to a known language-compiler pair.
- * @throws ValuesMapParseException
+ * @throws ValuesMapParseException if the value can't be parsed into a LanguageComilerSpecPair
*/
- public LanguageCompilerSpecPair parseLanguageCompileSpecPair(String val)
+ public LanguageCompilerSpecPair parseLanguageCompileSpecPair(String languageString)
throws ValuesMapParseException {
- if (val.isBlank()) {
+ if (languageString.isBlank()) {
return null;
}
// Split on last colon to get separated languageID and compilerSpecID
- int lastColon = val.lastIndexOf(':');
+ int lastColon = languageString.lastIndexOf(':');
if (lastColon < 1) {
throw new ValuesMapParseException(getName(), "Language/Compiler Spec",
- "Could not parse \"" + val + "\".");
+ "Could not parse \"" + languageString + "\".");
}
Set languages = getLanguagesCompilerPairs();
- String langId = val.substring(0, lastColon);
- String compilerId = val.substring(lastColon + 1);
+ String langId = languageString.substring(0, lastColon);
+ String compilerId = languageString.substring(lastColon + 1);
LanguageCompilerSpecPair storedLCS = new LanguageCompilerSpecPair(langId, compilerId);
if (!languages.contains(storedLCS)) {
throw new ValuesMapParseException(getName(), "Language/Compiler Spec",
- "Unknown language/Compiler Pair for \"" + val + "\"");
+ "Unknown language/Compiler Pair for \"" + languageString + "\"");
}
return storedLCS;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProgramFileValue.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProgramFileValue.java
new file mode 100644
index 0000000000..e9e0490f77
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProgramFileValue.java
@@ -0,0 +1,116 @@
+/* ###
+ * 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.features.base.values;
+
+import java.io.IOException;
+
+import javax.swing.JTextField;
+
+import docking.Tool;
+import docking.widgets.values.*;
+import ghidra.app.services.ProgramManager;
+import ghidra.framework.main.AppInfo;
+import ghidra.framework.main.DataTreeDialog;
+import ghidra.framework.model.DomainFile;
+import ghidra.framework.model.Project;
+import ghidra.program.model.listing.Program;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.exception.VersionException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * Value class for {@link Program} files. The editor component consists of the {@link JTextField}
+ * and a browse button for bringing up a {@link DataTreeDialog} for picking programs from the
+ * current project. This class also provides a convenience method for opening a program.
+ *
+ * This class and other subclasses of {@link AbstractValue} are part of a subsystem for easily
+ * defining a set of values that can be displayed in an input dialog ({@link ValuesMapDialog}).
+ * Typically, these values are created indirectly using a {@link GValuesMap} which is then
+ * given to the constructor of the dialog. However, an alternate approach is to create the
+ * dialog without a ValuesMap and then use its {@link ValuesMapDialog#addValue(AbstractValue)}
+ * method directly.
+ */
+public class ProgramFileValue extends ProjectFileValue {
+
+ /**
+ * Constructor for creating a new ProgramFileValue with the given name.
+ * @param name the name of the value
+ */
+ public ProgramFileValue(String name) {
+ this(name, null);
+ }
+
+ /**
+ * Constructor for creating a new ProgramFileValue with the given name and a starting
+ * folder when using the project file chooser.
+ * @param name the name of the value
+ * @param startingPath the path to a starting folder
+ */
+ public ProgramFileValue(String name, String startingPath) {
+ this(name, AppInfo.getActiveProject(), startingPath);
+ }
+
+ /**
+ * Constructor for ProgramValue when wanting to pick from a different project than the
+ * active project, such as a read-only project.
+ * @param name the name of the value
+ * @param project The project from which to pick a project.
+ * @param startingPath the path to a starting folder (Can also be a path to program)
+ */
+ public ProgramFileValue(String name, Project project, String startingPath) {
+ super(name, project, startingPath, Program.class);
+ }
+
+ /**
+ * Convenience method for opening the program for the current program file value. If the program
+ * is already open, then the consumer will be added to the program. The caller of this method is
+ * responsible for calling {@link Program#release(Object)} with the same consumer when it is
+ * done using this program. Program are only closed after all consumers are released. If
+ * multiple calls are made to this method, then the consumer will be added multiple times
+ * and must be released multiple times.
+ *
+ * The consumer can be any object, but since the consumer's purpose is to keep the program open
+ * while some object is using it, the object itself is typically passed in as
+ * the consumer. For example, when used in a script, passing in the java keyword "this" as the
+ * consumer will make the script itself the consumer.
+ *
+ * @param consumer the consumer to be used to open the program
+ * @param tool optional tool that if non-null, the program will also be opened in the tool
+ * @param monitor task monitor for cancelling the open program.
+ * @return a program for the current program value value. If the current program file value
+ * is null, then null will be returned.
+ * @throws VersionException if the Program being opened is an older version than the
+ * current Ghidra Program version.
+ * @throws IOException if there is an error accessing the Program's DomainObject
+ * @throws CancelledException if the operation is cancelled
+ */
+ public Program openProgram(Object consumer, Tool tool, TaskMonitor monitor)
+ throws VersionException, IOException, CancelledException {
+
+ DomainFile domainFile = getValue();
+ if (domainFile == null) {
+ return null;
+ }
+
+ Program program = (Program) domainFile.getDomainObject(consumer, false, false, monitor);
+
+ if (tool != null && program != null) {
+ tool.getService(ProgramManager.class).openProgram(program);
+ }
+ return program;
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProgramValue.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProgramValue.java
deleted file mode 100644
index d5ef2e5e18..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProgramValue.java
+++ /dev/null
@@ -1,153 +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.features.base.values;
-
-import java.io.IOException;
-
-import javax.swing.JComponent;
-import javax.swing.JTextField;
-
-import docking.Tool;
-import docking.widgets.values.*;
-import ghidra.app.services.ProgramManager;
-import ghidra.framework.main.DataTreeDialog;
-import ghidra.framework.model.DomainFile;
-import ghidra.framework.model.DomainObject;
-import ghidra.program.model.listing.Program;
-import ghidra.util.exception.CancelledException;
-import ghidra.util.exception.VersionException;
-import ghidra.util.task.TaskMonitor;
-
-/**
- * Value class for {@link Program}s. The editor component consists of the {@link JTextField} and
- * a browse button for bringing up a {@link DataTreeDialog} for picking programs from the
- * current project.
- *
- * This class and other subclasses of {@link AbstractValue} are part of a subsystem for easily
- * defining a set of values that can be displayed in an input dialog ({@link ValuesMapDialog}).
- * Typically, these values are created indirectly using a {@link GValuesMap} which is then
- * given to the constructor of the dialog. However, an alternate approach is to create the
- * dialog without a ValuesMap and then use its {@link ValuesMapDialog#addValue(AbstractValue)}
- * method directly.
- */
-public class ProgramValue extends AbstractValue {
-
- private ProjectBrowserPanel domainFilePanel;
- private Tool tool;
- private Object consumer;
-
- /**
- * Construct for ProgramValue
- * @param name the name of the value
- * @param consumer the program consumer to be used to open a program
- * @param tool if non null, the program will also be opened in this tool
- */
- public ProgramValue(String name, Object consumer, Tool tool) {
- this(name, null, consumer, tool);
- }
-
- /**
- * Construct for ProgramValue
- * @param name the name of the value
- * @param defaultValue the program to use as the default value
- * @param consumer the program consumer to be used to open a program
- * @param tool if non null, the program will also be opened in this tool
- */
- public ProgramValue(String name, Program defaultValue, Object consumer, Tool tool) {
- super(name, defaultValue);
- this.consumer = consumer;
- this.tool = tool;
- }
-
- @Override
- public JComponent getComponent() {
- if (domainFilePanel == null) {
- domainFilePanel = new ProjectBrowserPanel(getName(), false);
- }
- return domainFilePanel;
- }
-
- @Override
- protected void updateValueFromComponent() throws ValuesMapParseException {
- if (domainFilePanel != null) {
- DomainFile domainFile = domainFilePanel.getDomainFile();
- if (domainFile == null) {
- String text = domainFilePanel.getText();
- if (text.isBlank()) {
- setValue(null);
- return;
- }
- throw new ValuesMapParseException(getName(), "Program",
- "No file found for \"" + text + "\"");
- }
- Program program = openProgram(domainFile);
- setValue(program);
- }
- }
-
- private Program openProgram(DomainFile domainFile) throws ValuesMapParseException {
- if (domainFile == null) {
- return null;
- }
- Class extends DomainObject> domainObjectClass = domainFile.getDomainObjectClass();
- if (!Program.class.isAssignableFrom(domainObjectClass)) {
- return null;
- }
- try {
- Program program =
- (Program) domainFile.getDomainObject(consumer, false, false, TaskMonitor.DUMMY);
-
- if (tool != null && program != null) {
- tool.getService(ProgramManager.class).openProgram(program);
- }
- return program;
- }
- catch (VersionException | CancelledException | IOException e) {
- throw new ValuesMapParseException(getName(), "Program", e.getMessage());
- }
- }
-
- @Override
- protected void updateComponentFromValue() {
- Program program = getValue();
- DomainFile df = program == null ? null : program.getDomainFile();
- domainFilePanel.setDomainFile(df);
- }
-
- @Override
- protected Program fromString(String valueString) {
- DomainFile programFile = ProjectBrowserPanel.parseDomainFile(valueString);
- if (programFile == null) {
- throw new IllegalArgumentException("Could not find program " + valueString);
- }
- try {
- Program program = openProgram(programFile);
- if (program == null) {
- throw new IllegalArgumentException("Can't open program: " + valueString);
- }
- return program;
- }
- catch (ValuesMapParseException e) {
- throw new IllegalArgumentException(e.getMessage());
- }
- }
-
- @Override
- protected String toString(Program v) {
- return v.getDomainFile().getPathname();
- }
-
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFileValue.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFileValue.java
index 7af04f7690..a928e4e8ea 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFileValue.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFileValue.java
@@ -21,12 +21,11 @@ import javax.swing.JTextField;
import docking.widgets.values.*;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
-import ghidra.framework.model.DomainFile;
-import ghidra.framework.model.Project;
+import ghidra.framework.model.*;
import ghidra.framework.store.FileSystem;
/**
- * Value class for project files ({@link DomainFile}). The editor component consists of the
+ * Value class for project files ({@link DomainFile}). The editor component consists of a
* {@link JTextField} and a browse button for bringing up a {@link DataTreeDialog} for picking
* project files from the current project.
*
@@ -39,20 +38,61 @@ import ghidra.framework.store.FileSystem;
*/
public class ProjectFileValue extends AbstractValue {
- private ProjectBrowserPanel domainFilePanel;
+ private ProjectFileBrowserPanel domainFilePanel;
+ private String startingPath;
+ private Project project;
+ private Class extends DomainObject> projectFileClass;
+ /**
+ * Constructor for creating a new ProjectFileValue with the given name.
+ * @param name the name of the value
+ */
public ProjectFileValue(String name) {
- this(name, null);
+ this(name, AppInfo.getActiveProject(), null, DomainObject.class);
}
- public ProjectFileValue(String name, DomainFile defaultValue) {
- super(name, defaultValue);
+ /**
+ * Constructor for creating a new ProgramFileValue with the given name and {@link DomainObject}
+ * class to filter on (All other types will be filtered out in the chooser).
+ * @param name the name of the value
+ * @param projectFileClass the DomainObject class to filter
+ */
+ public ProjectFileValue(String name, Class extends DomainObject> projectFileClass) {
+ this(name, AppInfo.getActiveProject(), null, projectFileClass);
+ }
+
+ /**
+ * Constructor for creating a new ProjectFileValue with the given name and a starting
+ * folder when using the project file chooser.
+ * @param name the name of the value
+ * @param startingPath the path to a starting folder
+ */
+ public ProjectFileValue(String name, String startingPath) {
+ this(name, AppInfo.getActiveProject(), startingPath, DomainObject.class);
+ }
+
+ /**
+ * Constructor for ProgramValue when wanting to pick from a different project than the
+ * active project, such as a read-only project.
+ * @param name the name of the value
+ * @param project The project from which to pick a project.
+ * @param startingPath the path to a starting folder (Can also be a path to program)
+ * @param projectFileClass a {@link DomainFile} class to filter on. (Only those types
+ * will appear in the chooser)
+ */
+ public ProjectFileValue(String name, Project project, String startingPath,
+ Class extends DomainObject> projectFileClass) {
+ super(name, null);
+ this.project = project;
+ this.startingPath = startingPath;
+ this.projectFileClass = projectFileClass;
}
@Override
public JComponent getComponent() {
if (domainFilePanel == null) {
- domainFilePanel = new ProjectBrowserPanel(getName(), false);
+ domainFilePanel =
+ new ProjectFileBrowserPanel(project, getName(), startingPath, projectFileClass);
}
return domainFilePanel;
}
@@ -70,6 +110,11 @@ public class ProjectFileValue extends AbstractValue {
throw new ValuesMapParseException(getName(), "Project File",
"No file found for \"" + text + "\"");
}
+ Class extends DomainObject> domainObjectClass = domainFile.getDomainObjectClass();
+ if (!projectFileClass.isAssignableFrom(domainObjectClass)) {
+ throw new ValuesMapParseException(getName(), "Project File",
+ "Selected path is not a " + projectFileClass.getSimpleName());
+ }
setValue(domainFile);
}
}
@@ -87,6 +132,11 @@ public class ProjectFileValue extends AbstractValue {
if (df == null) {
throw new IllegalArgumentException("Can't find domain file: " + valueString);
}
+ Class extends DomainObject> domainObjectClass = df.getDomainObjectClass();
+ if (!projectFileClass.isAssignableFrom(domainObjectClass)) {
+ throw new IllegalArgumentException(
+ "Specified file path is not a " + projectFileClass.getSimpleName());
+ }
return df;
}
@@ -110,4 +160,44 @@ public class ProjectFileValue extends AbstractValue {
protected String toString(DomainFile v) {
return v.getPathname();
}
+
+ /**
+ * Component used by ProjectFileValues for picking project files
+ */
+ class ProjectFileBrowserPanel extends AbstractProjectBrowserPanel {
+
+ ProjectFileBrowserPanel(Project project, String name, String startPath,
+ Class extends DomainObject> projectFileClass) {
+ super(DataTreeDialog.OPEN, project, name, startPath);
+ filter = df -> projectFileClass.isAssignableFrom(df.getDomainObjectClass());
+ }
+
+ void setDomainFile(DomainFile value) {
+ String text = value == null ? "" : value.getPathname();
+ textField.setText(text);
+ }
+
+ @Override
+ protected void intializeCurrentValue(DataTreeDialog dialog) {
+ DomainFile current = getDomainFile();
+ if (current != null) {
+ dialog.selectDomainFile(current);
+ dialog.setNameText(current.getName());
+ }
+ }
+
+ @Override
+ protected String getSelectedPath(DataTreeDialog dialog) {
+ return dialog.getDomainFile().getPathname();
+ }
+
+ DomainFile getDomainFile() {
+ String text = textField.getText().trim();
+ if (text.isBlank()) {
+ return null;
+ }
+ return parseDomainFile(project, text);
+ }
+ }
+
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFolderValue.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFolderValue.java
index 742a284b61..1abb02ff64 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFolderValue.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/values/ProjectFolderValue.java
@@ -19,9 +19,9 @@ import javax.swing.JComponent;
import javax.swing.JTextField;
import docking.widgets.values.*;
+import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
-import ghidra.framework.model.DomainFile;
-import ghidra.framework.model.DomainFolder;
+import ghidra.framework.model.*;
/**
* Value class for project folders ({@link DomainFile}). The editor component consists of the
@@ -37,20 +37,42 @@ import ghidra.framework.model.DomainFolder;
*/
public class ProjectFolderValue extends AbstractValue {
- private ProjectBrowserPanel domainFilePanel;
+ private Project project;
+ private ProjectFolderBrowserPanel domainFilePanel;
+ /**
+ * Constructor for ProjectFolderValues with the given name.
+ * @param name the name of the value
+ */
public ProjectFolderValue(String name) {
this(name, null);
}
- public ProjectFolderValue(String name, DomainFolder defaultValue) {
- super(name, defaultValue);
+ /**
+ * Constructor for creating a new ProjectFolderValue with the given name and a path
+ * for a default folder value.
+ * @param name the name of the value
+ * @param defaultValuePath the path for a default folder value
+ */
+ public ProjectFolderValue(String name, String defaultValuePath) {
+ this(name, AppInfo.getActiveProject(), defaultValuePath);
+ }
+
+ /**
+ * Constructor for creating ProjectFolderValues for projects other than the active project.
+ * @param name the name of the value
+ * @param project the project to find a folder from
+ * @param defaultValuePath the path of a default folder value
+ */
+ public ProjectFolderValue(String name, Project project, String defaultValuePath) {
+ super(name, AbstractProjectBrowserPanel.parseDomainFolder(project, defaultValuePath));
+ this.project = project;
}
@Override
public JComponent getComponent() {
if (domainFilePanel == null) {
- domainFilePanel = new ProjectBrowserPanel(getName(), true);
+ domainFilePanel = new ProjectFolderBrowserPanel(project, getName(), null);
}
return domainFilePanel;
}
@@ -83,7 +105,7 @@ public class ProjectFolderValue extends AbstractValue {
@Override
protected DomainFolder fromString(String valueString) {
- DomainFolder df = ProjectBrowserPanel.parseDomainFolder(valueString);
+ DomainFolder df = AbstractProjectBrowserPanel.parseDomainFolder(project, valueString);
if (df == null) {
throw new IllegalArgumentException("Can't find domain folder: " + valueString);
}
@@ -95,4 +117,38 @@ public class ProjectFolderValue extends AbstractValue {
return v.getPathname();
}
+ /**
+ * Component used by ProjectFolderValues for picking project folders
+ */
+ class ProjectFolderBrowserPanel extends AbstractProjectBrowserPanel {
+
+ ProjectFolderBrowserPanel(Project project, String name, String startPath) {
+ super(DataTreeDialog.CHOOSE_FOLDER, project, name, startPath);
+ }
+
+ void setDomainFolder(DomainFolder value) {
+ String text = value == null ? "" : value.getPathname();
+ textField.setText(text);
+ }
+
+ @Override
+ protected void intializeCurrentValue(DataTreeDialog dialog) {
+ DomainFolder current = getDomainFolder();
+ dialog.selectFolder(current);
+ }
+
+ @Override
+ protected String getSelectedPath(DataTreeDialog dialog) {
+ return dialog.getDomainFolder().getPathname();
+ }
+
+ DomainFolder getDomainFolder() {
+ String text = textField.getText().trim();
+ if (text.isBlank()) {
+ return parseDomainFolder(project, "/");
+ }
+ return parseDomainFolder(project, text);
+ }
+ }
+
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java
index f798c1b33a..2755f84f72 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java
@@ -91,10 +91,11 @@ public class DataTreeDialog extends DialogComponentProvider
private ProjectDataNewFolderAction newFolderAction;
private Integer treeSelectionMode;
+ private final Project project;
/**
- * Construct a new DataTreeDialog. This chooser will show all project files.
- * Following linked-folders will only be allowed if a type of {@link #CHOOSE_FOLDER}
+ * Construct a new DataTreeDialog for the active project. This chooser will show all project
+ * files. Following linked-folders will only be allowed if a type of {@link #CHOOSE_FOLDER}
* or {@link #OPEN} is specified. If different behavior is required a filter should
* be specified using the other constructor.
*
@@ -104,11 +105,11 @@ public class DataTreeDialog extends DialogComponentProvider
* @throws IllegalArgumentException if invalid type is specified
*/
public DataTreeDialog(Component parent, String title, int type) {
- this(parent, title, type, getDefaultFilter(type));
+ this(parent, title, type, getDefaultFilter(type), AppInfo.getActiveProject());
}
/**
- * Construct a new DataTreeDialog.
+ * Construct a new DataTreeDialog for the active project.
*
* @param parent dialog's parent
* @param title title to use
@@ -117,7 +118,23 @@ public class DataTreeDialog extends DialogComponentProvider
* @throws IllegalArgumentException if invalid type is specified
*/
public DataTreeDialog(Component parent, String title, int type, DomainFileFilter filter) {
+ this(parent, title, type, filter, AppInfo.getActiveProject());
+ }
+
+ /**
+ * Construct a new DataTreeDialog for the given project.
+ *
+ * @param parent dialog's parent
+ * @param title title to use
+ * @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CHOOSE_USER_FOLDER
+ * @param filter filter used to control what is displayed in the data tree
+ * @param project the project to browse
+ * @throws IllegalArgumentException if invalid type is specified
+ */
+ public DataTreeDialog(Component parent, String title, int type, DomainFileFilter filter,
+ Project project) {
super(title, true, true, true, false);
+ this.project = project;
this.parent = parent;
initDataTreeDialog(type, filter);
}
@@ -230,7 +247,6 @@ public class DataTreeDialog extends DialogComponentProvider
comboModelInitialized = true;
// repopulate the tree
- Project project = AppInfo.getActiveProject();
ProjectData pd = project.getProjectData();
treePanel.setProjectData(project.getName(), pd);
@@ -373,7 +389,7 @@ public class DataTreeDialog extends DialogComponentProvider
nameField.setText(domainFolder.getName());
}
else {
- domainFolder = AppInfo.getActiveProject().getProjectData().getRootFolder();
+ domainFolder = project.getProjectData().getRootFolder();
folderNameLabel.setText(domainFolder.getPathname());
nameField.setText(domainFolder.getName());
}
@@ -389,7 +405,7 @@ public class DataTreeDialog extends DialogComponentProvider
else {
domainFolder = treePanel.getSelectedDomainFolder();
if (domainFolder == null) {
- domainFolder = AppInfo.getActiveProject().getProjectData().getRootFolder();
+ domainFolder = project.getProjectData().getRootFolder();
}
folderNameLabel.setText(domainFolder.getPathname());
@@ -415,7 +431,6 @@ public class DataTreeDialog extends DialogComponentProvider
return;
}
- Project project = AppInfo.getActiveProject();
ProjectLocator projectLocator = projectLocators[index];
ProjectData pd = project.getProjectData(projectLocator);
String projectName = projectLocator.getName();
@@ -511,7 +526,6 @@ public class DataTreeDialog extends DialogComponentProvider
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
- Project project = AppInfo.getActiveProject();
ProjectData pd = project.getProjectData();
treePanel = new ProjectDataTreePanel(project.getName(), true, // is for the active project
@@ -646,7 +660,6 @@ public class DataTreeDialog extends DialogComponentProvider
}
private void populateProjectModel() {
- Project project = AppInfo.getActiveProject();
ProjectLocator[] views = project.getProjectViews();
projectLocators = new ProjectLocator[views.length + 1];
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AbstractValueIntegrationTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AbstractValueIntegrationTest.java
index 04ef8ef681..ca3d095c12 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AbstractValueIntegrationTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/AbstractValueIntegrationTest.java
@@ -15,6 +15,8 @@
*/
package ghidra.features.base.values;
+import static org.junit.Assert.*;
+
import javax.swing.JButton;
import javax.swing.JTextField;
@@ -25,13 +27,14 @@ import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.values.AbstractValue;
import docking.widgets.values.ValuesMapDialog;
-import ghidra.features.base.values.GhidraValuesMap;
-import ghidra.features.base.values.ProjectBrowserPanel;
+import ghidra.features.base.values.ProjectFileValue.ProjectFileBrowserPanel;
+import ghidra.features.base.values.ProjectFolderValue.ProjectFolderBrowserPanel;
+import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.*;
import ghidra.program.database.ProgramBuilder;
-import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*;
+import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.task.TaskMonitor;
@@ -43,30 +46,47 @@ public abstract class AbstractValueIntegrationTest extends AbstractGhidraHeadedI
protected TestEnv env;
protected DomainFolder rootFolder;
- protected DomainFolder folder;
+ protected DomainFolder folderA;
+ protected DomainFolder folderB;
protected DomainFile fileA;
protected DomainFile fileB;
- protected ProgramDB programA;
- protected ProgramDB programB;
+ protected DomainFile fileC;
+ protected Program programA;
+ protected Program programB;
+ protected Project project;
@Before
public void setup() throws Exception {
env = new TestEnv();
- Project project = env.getProject();
- runSwing(() -> env.getFrontEndTool().setActiveProject(project));
+ project = env.getProject();
+ AppInfo.setActiveProject(project);
rootFolder = project.getProjectData().getRootFolder();
- folder = rootFolder.createFolder("A");
- ProgramBuilder programBuilderA = new ProgramBuilder("A", ProgramBuilder._TOY);
- ProgramBuilder programBuilderB = new ProgramBuilder("B", ProgramBuilder._TOY);
+ folderA = rootFolder.createFolder("A");
+ folderB = rootFolder.createFolder("B");
+ ProgramBuilder programBuilderA = new ProgramBuilder("A", ProgramBuilder._TOY, this);
+ ProgramBuilder programBuilderB = new ProgramBuilder("B", ProgramBuilder._TOY, this);
+ ProgramBuilder programBuilderC = new ProgramBuilder("C", ProgramBuilder._TOY, this);
programA = programBuilderA.getProgram();
programB = programBuilderB.getProgram();
- fileA = folder.createFile("A", programA, TaskMonitor.DUMMY);
- fileB = folder.createFile("B", programB, TaskMonitor.DUMMY);
+ Program programC = programBuilderC.getProgram();
+ fileA = folderA.createFile("A", programA, TaskMonitor.DUMMY);
+ fileB = folderA.createFile("B", programB, TaskMonitor.DUMMY);
+ fileC = folderA.createFile("C", programC, TaskMonitor.DUMMY);
+ programBuilderC.dispose(); // closes program C
+ programC.release(this);
+ assertTrue(programC.isClosed());
}
@After
public void tearDown() {
+ // some tests close the programs
+ if (!programA.isClosed()) {
+ programA.release(this);
+ }
+ if (!programB.isClosed()) {
+ programB.release(this);
+ }
env.dispose();
}
@@ -91,7 +111,7 @@ public abstract class AbstractValueIntegrationTest extends AbstractGhidraHeadedI
}
protected void setProjectFileOnProjectTree(AbstractValue> value, DomainFile file) {
- ProjectBrowserPanel projectWidget = (ProjectBrowserPanel) value.getComponent();
+ ProjectFileBrowserPanel projectWidget = (ProjectFileBrowserPanel) value.getComponent();
pressButtonByName(projectWidget, "BrowseButton", false);
DataTreeDialog dataTreeDialog = waitForDialogComponent(DataTreeDialog.class);
@@ -104,7 +124,7 @@ public abstract class AbstractValueIntegrationTest extends AbstractGhidraHeadedI
}
protected void setProjectFolderOnProjectTree(AbstractValue> value, DomainFolder folder) {
- ProjectBrowserPanel projectWidget = (ProjectBrowserPanel) value.getComponent();
+ ProjectFolderBrowserPanel projectWidget = (ProjectFolderBrowserPanel) value.getComponent();
pressButtonByName(projectWidget, "BrowseButton", false);
DataTreeDialog dataTreeDialog = waitForDialogComponent(DataTreeDialog.class);
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProgramValueTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProgramFileValueTest.java
similarity index 54%
rename from Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProgramValueTest.java
rename to Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProgramFileValueTest.java
index d78e1603d2..3ec8026472 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProgramValueTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProgramFileValueTest.java
@@ -20,16 +20,15 @@ import static org.junit.Assert.*;
import org.junit.Test;
import ghidra.app.services.ProgramManager;
-import ghidra.features.base.values.ProgramValue;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
-public class ProgramValueTest extends AbstractValueIntegrationTest {
+public class ProgramFileValueTest extends AbstractValueIntegrationTest {
private static final String NAME = "Program";
@Test
- public void testProgramValueNoDefault() {
- values.defineProgram(NAME, this, null);
+ public void testProgramValueNoDefault() throws Exception {
+ values.defineProgram(NAME);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
@@ -37,38 +36,41 @@ public class ProgramValueTest extends AbstractValueIntegrationTest {
values.setProgram(NAME, programA);
assertTrue(values.hasValue(NAME));
- assertEquals(programA, values.getProgram(NAME));
+ assertEquals(programA, values.getProgram(NAME, this, null));
}
@Test
- public void testProgramValueWithDefault() {
- values.defineProgram(NAME, programA, this, null);
+ public void testSetValue() throws Exception {
+ values.defineProgram(NAME);
+ values.setProgram(NAME, programA);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
- assertEquals(programA, values.getProgram(NAME));
+ assertEquals(programA, values.getProgram(NAME, this, null));
values.setProgram(NAME, programB);
assertTrue(values.hasValue(NAME));
- assertEquals(programB, values.getProgram(NAME));
+ assertEquals(programB, values.getProgram(NAME, this, null));
values.setProgram(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
- public void testGetAsText() {
- ProgramValue value1 = new ProgramValue(NAME, this, null);
- ProgramValue value2 = new ProgramValue(NAME, programA, this, null);
- assertNull(value1.getAsText());
- assertEquals("/A/A", value2.getAsText());
+ public void testGetAsText() throws Exception {
+ ProgramFileValue value = values.defineProgram(NAME);
+ assertNull(value.getAsText());
+
+ values.setProgram(NAME, programA);
+
+ assertEquals("/A/A", value.getAsText());
}
@Test
public void testSetAsText() {
- ProgramValue v = new ProgramValue(NAME, this, null);
- assertEquals(programA, v.setAsText("/A/A"));
+ ProgramFileValue v = new ProgramFileValue(NAME);
+ assertEquals(programA.getDomainFile(), v.setAsText("/A/A"));
try {
v.setAsText(null);
fail("Expected exception");
@@ -86,20 +88,20 @@ public class ProgramValueTest extends AbstractValueIntegrationTest {
}
@Test
- public void testNoDefaultValueWithNoDialogInput() {
- values.defineProgram(NAME, this, null);
+ public void testNoDefaultValueWithNoDialogInput() throws Exception {
+ values.defineProgram(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
- assertNull(values.getProgram(NAME));
+ assertNull(values.getProgram(NAME, this, null));
}
@Test
- public void testNoDefaultValueWithDialogInput() {
- values.defineProgram(NAME, this, null);
+ public void testNoDefaultValueWithDialogInput() throws Exception {
+ values.defineProgram(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
@@ -107,49 +109,67 @@ public class ProgramValueTest extends AbstractValueIntegrationTest {
pressOk();
assertTrue(values.hasValue(NAME));
- assertEquals(programA, values.getProgram(NAME));
+ assertEquals(programA, values.getProgram(NAME, this, null));
}
@Test
- public void testDefaultValueWithNoDialogInput() {
- values.defineProgram(NAME, programA, this, null);
- assertTrue(values.hasValue(NAME));
+ public void testExistingValueWithNoDialogInput() throws Exception {
+ values.defineProgram(NAME);
+ values.setProgram(NAME, programA);
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
- assertEquals(programA, values.getProgram(NAME));
+ assertEquals(programA, values.getProgram(NAME, this, null));
}
@Test
- public void testDefaultValueWithDialogInput() {
- values.defineProgram(NAME, programA, this, null);
- assertTrue(values.hasValue(NAME));
+ public void testDefaultValueWithDialogInput() throws Exception {
+ values.defineProgram(NAME);
+ values.setProgram(NAME, programA);
showDialogOnSwingWithoutBlocking();
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programB.getDomainFile());
pressOk();
assertTrue(values.hasValue(NAME));
- assertEquals(programB, values.getProgram(NAME));
+ assertEquals(programB, values.getProgram(NAME, this, null));
}
@Test
- public void testOpenProgramInTool() {
+ public void testOpenProgramInTool() throws Exception {
PluginTool tool = env.createDefaultTool();
ProgramManager programManagerService = tool.getService(ProgramManager.class);
Program[] allOpenPrograms = programManagerService.getAllOpenPrograms();
assertEquals(0, allOpenPrograms.length);
- values.defineProgram(NAME, this, tool);
+ values.defineProgram(NAME);
showDialogOnSwingWithoutBlocking();
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
pressOk();
+ Program p = values.getProgram(NAME, this, tool);
+
allOpenPrograms = programManagerService.getAllOpenPrograms();
assertEquals(1, allOpenPrograms.length);
- assertEquals(programA, allOpenPrograms[0]);
+ assertEquals(p, allOpenPrograms[0]);
}
+ @Test
+ public void testOpenProgramMutltipleTimes() throws Exception {
+ values.defineProgram(NAME);
+ assertEquals(1, programA.getConsumerList().size());
+ showDialogOnSwingWithoutBlocking();
+ setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
+ pressOk();
+
+ Program p1 = values.getProgram(NAME, this, null);
+ assertEquals(2, programA.getConsumerList().size());
+ Program p2 = values.getProgram(NAME, this, null);
+ assertEquals(p1, p2);
+ assertEquals(3, programA.getConsumerList().size());
+ p1.release(this);
+ p2.release(this);
+ }
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFileValueTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFileValueTest.java
index 9dd86d5a48..786fe0dea3 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFileValueTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFileValueTest.java
@@ -19,8 +19,6 @@ import static org.junit.Assert.*;
import org.junit.Test;
-import ghidra.features.base.values.ProjectFileValue;
-
public class ProjectFileValueTest extends AbstractValueIntegrationTest {
private static final String NAME = "Project File";
@@ -39,7 +37,8 @@ public class ProjectFileValueTest extends AbstractValueIntegrationTest {
@Test
public void testProjectFileValueWithDefault() {
- values.defineProjectFile(NAME, fileA);
+ values.defineProjectFile(NAME);
+ values.setProjectFile(NAME, fileA);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
@@ -57,7 +56,9 @@ public class ProjectFileValueTest extends AbstractValueIntegrationTest {
@Test
public void testGetAsText() {
ProjectFileValue value1 = new ProjectFileValue(NAME);
- ProjectFileValue value2 = new ProjectFileValue(NAME, fileA);
+ ProjectFileValue value2 = new ProjectFileValue(NAME);
+ value2.setValue(fileA);
+
assertNull(value1.getAsText());
assertEquals("/A/A", value2.getAsText());
}
@@ -109,7 +110,8 @@ public class ProjectFileValueTest extends AbstractValueIntegrationTest {
@Test
public void testDefaultValueWithNoDialogInput() {
- values.defineProjectFile(NAME, fileA);
+ values.defineProjectFile(NAME);
+ values.setProjectFile(NAME, fileA);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
@@ -121,7 +123,8 @@ public class ProjectFileValueTest extends AbstractValueIntegrationTest {
@Test
public void testDefaultValueWithDialogInput() {
- values.defineProjectFile(NAME, fileA);
+ values.defineProjectFile(NAME);
+ values.setProjectFile(NAME, fileA);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFolderValueTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFolderValueTest.java
index 6104094f95..f17d1334f0 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFolderValueTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/features/base/values/ProjectFolderValueTest.java
@@ -19,8 +19,6 @@ import static org.junit.Assert.*;
import org.junit.Test;
-import ghidra.features.base.values.ProjectFolderValue;
-
public class ProjectFolderValueTest extends AbstractValueIntegrationTest {
private static final String NAME = "Project File";
@@ -31,24 +29,24 @@ public class ProjectFolderValueTest extends AbstractValueIntegrationTest {
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
- values.setProjectFolder(NAME, folder);
+ values.setProjectFolder(NAME, folderA);
assertTrue(values.hasValue(NAME));
- assertEquals(folder, values.getProjectFolder(NAME));
+ assertEquals(folderA, values.getProjectFolder(NAME));
}
@Test
public void testProjectFolderValueWithDefault() {
- values.defineProjectFolder(NAME, rootFolder);
+ values.defineProjectFolder(NAME, "/A");
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
- assertEquals(rootFolder, values.getProjectFolder(NAME));
+ assertEquals(folderA, values.getProjectFolder(NAME));
- values.setProjectFolder(NAME, folder);
+ values.setProjectFolder(NAME, folderB);
assertTrue(values.hasValue(NAME));
- assertEquals(folder, values.getProjectFolder(NAME));
+ assertEquals(folderB, values.getProjectFolder(NAME));
values.setProjectFolder(NAME, null);
assertFalse(values.hasValue(NAME));
@@ -57,7 +55,7 @@ public class ProjectFolderValueTest extends AbstractValueIntegrationTest {
@Test
public void testGetAsText() {
ProjectFolderValue value1 = new ProjectFolderValue(NAME);
- ProjectFolderValue value2 = new ProjectFolderValue(NAME, folder);
+ ProjectFolderValue value2 = new ProjectFolderValue(NAME, "/A");
assertNull(value1.getAsText());
assertEquals("/A", value2.getAsText());
}
@@ -65,7 +63,7 @@ public class ProjectFolderValueTest extends AbstractValueIntegrationTest {
@Test
public void testSetAsText() {
ProjectFolderValue v = new ProjectFolderValue(NAME);
- assertEquals(folder, v.setAsText("/A"));
+ assertEquals(folderA, v.setAsText("/A"));
try {
v.setAsText(null);
fail("Expected exception");
@@ -102,36 +100,36 @@ public class ProjectFolderValueTest extends AbstractValueIntegrationTest {
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
- setProjectFolderOnProjectTree(values.getAbstractValue(NAME), folder);
+ setProjectFolderOnProjectTree(values.getAbstractValue(NAME), folderA);
pressOk();
assertTrue(values.hasValue(NAME));
- assertEquals(folder, values.getProjectFolder(NAME));
+ assertEquals(folderA, values.getProjectFolder(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
- values.defineProjectFolder(NAME, folder);
+ values.defineProjectFolder(NAME, "/A");
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
- assertEquals(folder, values.getProjectFolder(NAME));
+ assertEquals(folderA, values.getProjectFolder(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
- values.defineProjectFolder(NAME, rootFolder);
+ values.defineProjectFolder(NAME, "/A");
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
- setProjectFolderOnProjectTree(values.getAbstractValue(NAME), folder);
+ setProjectFolderOnProjectTree(values.getAbstractValue(NAME), folderB);
pressOk();
assertTrue(values.hasValue(NAME));
- assertEquals(folder, values.getProjectFolder(NAME));
+ assertEquals(folderB, values.getProjectFolder(NAME));
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/AbstractValue.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/AbstractValue.java
index 34fce45f78..e8410f667f 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/AbstractValue.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/AbstractValue.java
@@ -40,6 +40,7 @@ import javax.swing.JComponent;
public abstract class AbstractValue {
private final String name;
private T value;
+ private T originalValue;
/**
* Constructor that assigned a name and optional initial value for this object.
@@ -49,6 +50,7 @@ public abstract class AbstractValue {
protected AbstractValue(String name, T defaultValue) {
this.name = Objects.requireNonNull(name);
this.value = defaultValue;
+ this.originalValue = defaultValue;
}
/**
@@ -116,6 +118,13 @@ public abstract class AbstractValue {
return value == null ? null : toString(value);
}
+ /**
+ * Resets the value to its original value when constructed
+ */
+ protected void reset() {
+ value = originalValue;
+ }
+
protected String toString(T t) {
return t.toString();
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/GValuesMap.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/GValuesMap.java
index d848a8953b..4d24a70cd9 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/GValuesMap.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/GValuesMap.java
@@ -553,4 +553,14 @@ public class GValuesMap {
}
}
+ /**
+ * Resets the values back to their original values when constructed. Used by the dialog
+ * when the user cancels.
+ */
+ protected void reset() {
+ for (AbstractValue> inputValue : valuesMap.values()) {
+ inputValue.reset();
+ }
+ }
+
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/ValuesMapDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/ValuesMapDialog.java
index 049e7d6839..6dbcdf9d44 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/ValuesMapDialog.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/values/ValuesMapDialog.java
@@ -133,6 +133,7 @@ public class ValuesMapDialog extends DialogComponentProvider {
@Override
protected void cancelCallback() {
+ valuesMap.reset();
cancelled = true;
super.cancelCallback();
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java
index 3c2fef7ed5..2b75e7162a 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java
@@ -313,11 +313,6 @@ public class DomainFileProxy implements DomainFile {
return true;
}
- public boolean isUsedExclusivelyBy(Object consumer) {
- DomainObjectAdapter dobj = getDomainObject();
- return dobj != null ? dobj.isUsedExclusivelyBy(consumer) : false;
- }
-
@Override
public ArrayList> getConsumers() {
DomainObjectAdapter dobj = getDomainObject();
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java
index 6a02a0c234..c391c727fd 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java
@@ -333,19 +333,12 @@ public abstract class DomainObjectAdapter implements DomainObject {
@Override
public boolean addConsumer(Object consumer) {
- if (consumer == null) {
- throw new IllegalArgumentException("Consumer must not be null");
- }
+ Objects.requireNonNull(consumer);
synchronized (consumers) {
if (isClosed()) {
return false;
}
-
- if (consumers.contains(consumer)) {
- throw new IllegalArgumentException("Attempted to acquire the " +
- "domain object more than once by the same consumer: " + consumer);
- }
consumers.add(consumer);
}
@@ -359,18 +352,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
}
/**
- * Returns true if the this file is used only by the given consumer
- * @param consumer the consumer
- * @return true if the this file is used only by the given consumer
- */
- boolean isUsedExclusivelyBy(Object consumer) {
- synchronized (consumers) {
- return (consumers.size() == 1) && (consumers.contains(consumer));
- }
- }
-
- /**
- * Returns true if the given tool is using this object.
+ * Returns true if the given consumer is using this object.
*/
@Override
public boolean isUsedBy(Object consumer) {
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/AppInfo.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/AppInfo.java
index 17e89cbf9d..ab59f1afec 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/AppInfo.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/AppInfo.java
@@ -31,7 +31,7 @@ public class AppInfo {
tool = t;
}
- static void setActiveProject(Project p) {
+ public static void setActiveProject(Project p) {
activeProject = p;
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java
index f05da0e53c..3c17e4b5b7 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObject.java
@@ -17,7 +17,8 @@ package ghidra.framework.model;
import java.io.File;
import java.io.IOException;
-import java.util.*;
+import java.util.List;
+import java.util.Map;
import ghidra.framework.options.Options;
import ghidra.util.ReadOnlyException;
@@ -216,7 +217,7 @@ public interface DomainObject {
* Returns the list of consumers on this domainObject
* @return the list of consumers.
*/
- public ArrayList