GP-3992 fixed various issues/bugs related to the new askValues() script method

This commit is contained in:
ghidragon 2023-11-06 15:40:38 -05:00
parent 134806cbe4
commit 9cd2666799
22 changed files with 556 additions and 383 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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
@ -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());

View file

@ -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;
}
}

View file

@ -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.
* <P>
* 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.
* <P>
* @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());
}
/**

View file

@ -91,33 +91,33 @@ public class LanguageValue extends AbstractValue<LanguageCompilerSpecPair> {
/**
* 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<LanguageCompilerSpecPair> 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;
}

View file

@ -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.
* <P>
* 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.
* <P>
* 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.
* <P>
* @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;
}
}

View file

@ -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.
* <P>
* 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<Program> {
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();
}
}

View file

@ -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.
* <P>
@ -39,20 +38,61 @@ import ghidra.framework.store.FileSystem;
*/
public class ProjectFileValue extends AbstractValue<DomainFile> {
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<DomainFile> {
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<DomainFile> {
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<DomainFile> {
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);
}
}
}

View file

@ -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<DomainFolder> {
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<DomainFolder> {
@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<DomainFolder> {
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);
}
}
}

View file

@ -91,10 +91,11 @@ public class DataTreeDialog extends DialogComponentProvider
private ProjectDataNewFolderAction<DialogProjectTreeContext> 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];

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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));
}
}

View file

@ -40,6 +40,7 @@ import javax.swing.JComponent;
public abstract class AbstractValue<T> {
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<T> {
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<T> {
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();
}

View file

@ -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();
}
}
}

View file

@ -133,6 +133,7 @@ public class ValuesMapDialog extends DialogComponentProvider {
@Override
protected void cancelCallback() {
valuesMap.reset();
cancelled = true;
super.cancelCallback();
}

View file

@ -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();

View file

@ -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) {

View file

@ -31,7 +31,7 @@ public class AppInfo {
tool = t;
}
static void setActiveProject(Project p) {
public static void setActiveProject(Project p) {
activeProject = p;
}

View file

@ -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<Object> getConsumerList();
public List<Object> getConsumerList();
/**
* Returns true if the given consumer is using (has open) this domain object.