GP-3924 Added askValues() method to GhidraScript. Allows users to enter

multiple values with one dialog.
This commit is contained in:
ghidragon 2023-10-17 15:22:21 -04:00
parent 87bd074603
commit e71d5f8faf
45 changed files with 4856 additions and 235 deletions

View file

@ -0,0 +1,92 @@
/* ###
* 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.
*/
// Example script for showing how to use the "AskValues" script method for inputing multiple values
// @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;
public class AskValuesExampleScript extends GhidraScript {
@Override
public void run() throws Exception {
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());
// 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.
// It also includes a statusListener where messages can be set on the dialog. In this
// example, we are requiring that the name and program fields be populated
values.setValidator((valueMap, status) -> {
if (!valueMap.hasValue("Name")) {
status.setStatusText("Name must be filled in!", MessageType.ERROR);
return false;
}
if (!valueMap.hasValue("Other Program")) {
status.setStatusText("Other Program must be filled it!", MessageType.ERROR);
return false;
}
return true;
});
// asks the script to show a dialog where the user can give values for all the items
// in the ValuesMap.
values = askValues("Enter Example Script Values", null, values);
// if the user cancels the ask dialog, the script will exit as cancelled. Otherwise
// the returned ValuesMap will contain the results of the user filling in values from the
//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");
println("Name = " + name);
println("Address = " + address);
println("Count = " + age);
println("Max Results = " + max);
println("Priority = " + priority);
println("Program = " + program);
// VERY IMPORTANT!!! you must release any programs when you are done with them!
// If you also opened in the tool, you can immediately release it because the tool will
// then keep it open.
program.release(this);
}
}

View file

@ -33,8 +33,7 @@ public class ReloadSleighLanguage extends GhidraScript {
language.reloadLanguage(monitor);
}
catch (IOException e) {
Msg.showError(this, this.state.getParamPanel(), "Reload Sleigh Language Failed",
e.getMessage());
Msg.showError(this, null, "Reload Sleigh Language Failed", e.getMessage());
return;
}
currentProgram.setLanguage(language, null, true, monitor);

View file

@ -1,189 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.script;
import java.awt.Component;
import java.awt.GridLayout;
import java.io.File;
import java.util.HashMap;
import javax.swing.JPanel;
import javax.swing.JTextField;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.filechooser.GhidraFileChooserPanel;
import docking.widgets.label.GLabel;
import ghidra.app.util.AddressInput;
public class GatherParamPanel extends JPanel {
private static final long serialVersionUID = 1L;
public static final int STRING = 0;
public static final int FILE = 1;
public static final int DIRECTORY = 2;
public static final int ADDRESS = 3;
public static final int INTEGER = 4;
public static final int LANGUAGE = 5;
private GhidraState state;
private HashMap<String, ParamComponent> parameters;
private boolean shown;
public GatherParamPanel(GhidraState state) {
this.state = state;
setLayout(new GridLayout(0, 2));
parameters = new HashMap<>();
shown = false;
}
public ParamComponent getParameter(String key) {
return parameters.get(key);
}
public void clearParameters() {
parameters.clear();
removeAll();
}
public void addParameterRegardless(String key, String label, int type, Object defaultValue) {
Component displayComponent = null;
if (type == FILE || type == DIRECTORY) {
String titleString = null;
if (type == DIRECTORY) {
titleString = "SELECT DIRECTORY";
}
else {
titleString = "SELECT FILE";
}
GhidraFileChooserPanel panel = new GhidraFileChooserPanel(titleString,
"Recipe.fileChooser", "", true, GhidraFileChooserPanel.INPUT_MODE);
if (type == DIRECTORY) {
panel.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
}
panel.setFileName(defaultValue.toString());
parameters.put(key, new ParamComponent(panel, type));
displayComponent = panel;
}
else if (type == ADDRESS) {
AddressInput addressInput = new AddressInput();
if (state.getCurrentProgram() != null) {
addressInput.setAddressFactory(state.getCurrentProgram().getAddressFactory());
}
addressInput.selectDefaultAddressSpace();
addressInput.select();
if (defaultValue != null) {
addressInput.setValue(defaultValue.toString());
}
displayComponent = addressInput;
parameters.put(key, new ParamComponent(displayComponent, type));
}
else {
JTextField textField = new JTextField();
if (defaultValue != null) {
textField.setText(defaultValue.toString());
}
displayComponent = textField;
parameters.put(key, new ParamComponent(displayComponent, type));
}
add(new GLabel(label));
add(displayComponent);
shown = false;
}
public void addParameter(String key, String label, int type, Object defaultValue) {
if (parameters.containsKey(key) || state.getEnvironmentVar(key) != null) {
return;
}
addParameterRegardless(key, label, type, defaultValue);
}
public void setParamsInState() {
for (String string2 : parameters.keySet()) {
String key = string2.toString();
ParamComponent pc = parameters.get(key);
switch (pc.getType()) {
case ADDRESS:
if (state.getCurrentProgram() != null) {
AddressInput addressInput = (AddressInput) pc.getDisplayComponent();
state.addEnvironmentVar(key, addressInput.getAddress());
}
else {
AddressInput addressInput = (AddressInput) pc.getDisplayComponent();
state.addEnvironmentVar(key, addressInput.getValue().toString());
}
break;
case FILE:
case DIRECTORY:
GhidraFileChooserPanel gfcp = (GhidraFileChooserPanel) pc.getDisplayComponent();
state.addEnvironmentVar(key, new File(gfcp.getFileName()));
break;
case INTEGER:
JTextField iTextField = (JTextField) pc.getDisplayComponent();
int val = Integer.parseInt(iTextField.getText());
state.addEnvironmentVar(key, val);
break;
default:
JTextField textField = (JTextField) pc.getDisplayComponent();
state.addEnvironmentVar(key, textField.getText());
break;
}
}
}
public void currentProgramChanged() {
for (String string2 : parameters.keySet()) {//OMG!!
String key = string2.toString();
ParamComponent pc = parameters.get(key);
switch (pc.getType()) {
case ADDRESS:
AddressInput addressInput = (AddressInput) pc.getDisplayComponent();
addressInput.setAddressFactory(state.getCurrentProgram().getAddressFactory());
addressInput.selectDefaultAddressSpace();
addressInput.select();
if (panelShown()) {
state.addEnvironmentVar(key, addressInput.getAddress());
}
break;
}
}
}
public boolean panelShown() {
return shown;
}
public void setShown(boolean shown) {
this.shown = shown;
}
public class ParamComponent {
private int type;
private Component displayComponent;
public ParamComponent(Component displayComponent, int type) {
this.displayComponent = displayComponent;
this.type = type;
}
public Component getDisplayComponent() {
return displayComponent;
}
public int getType() {
return type;
}
}
}

View file

@ -20,12 +20,15 @@ import java.io.*;
import java.rmi.ConnectException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import docking.DockingWindowManager;
import docking.widgets.OptionDialog;
import docking.widgets.PasswordDialog;
import docking.widgets.dialogs.MultiLineMessageDialog;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.values.*;
import generic.jar.ResourceFile;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.plugin.core.analysis.AnalysisWorker;
@ -44,6 +47,7 @@ import ghidra.app.util.opinion.*;
import ghidra.app.util.query.TableService;
import ghidra.app.util.viewer.field.BrowserCodeUnitFormat;
import ghidra.app.util.viewer.field.CommentUtils;
import ghidra.features.base.values.GhidraValuesMap;
import ghidra.framework.Application;
import ghidra.framework.client.*;
import ghidra.framework.cmd.BackgroundCommand;
@ -2327,6 +2331,66 @@ public abstract class GhidraScript extends FlatProgramAPI {
}
}
/**
* Prompts for multiple values at the same time. To use this method, you must first
* create a {@link GhidraValuesMap} and define the values that will be supplied by this method.
* In the GUI environment, this will result in a single dialog with an entry for each value
* defined in the values map. This method returns a GhidraValuesMap with the values supplied by
* the user in GUI mode or command line arguments in headless mode. If the user cancels the
* dialog, a cancelled exception will be thrown, and unless it is explicity caught by the
* script, will terminate the script. Also, if the values map has a {@link ValuesMapValidator},
* the values will be validated when the user presses the "OK" button and will only exit the
* dialog if the validate check passes. Otherwise, the validator should have reported an error
* message in the dialog and the dialog will remain visible.
*
* <p>
* Regardless of environment -- if script arguments have been set, this method will use the
* next arguments in the array and advance the array index until all values in the values map
* have been satisfied and so the next call to an ask method will get the next argument after
* those consumed by this call.
*
* @param title the title of the dialog if in GUI mode
* @param optionalMessage an optional message that is displayed in the dialog, just above the
* list of name/value pairs
* @param values the GhidraValuesMap containing the values to include in the dialog.
* @return the GhidraValuesMap with values set from user input in the dialog (This is the same
* instance that was passed in, so you don't need to use this)
* @throws CancelledException if the user hit the 'cancel' button in GUI mode
*/
public GhidraValuesMap askValues(String title, String optionalMessage, GhidraValuesMap values)
throws CancelledException {
for (AbstractValue<?> value : values.getValues()) {
String key = join(title, value.getName());
loadAskValue(value.getValue(), s -> value.setAsText(s), key);
}
if (isRunningHeadless()) {
return values;
}
String key = generateKey(values);
return doAsk(GValuesMap.class, title, key, values, v -> {
if (v != values) {
values.copyValues(v);
}
ValuesMapDialog dialog = new ValuesMapDialog(title, optionalMessage, values);
DockingWindowManager.showDialog(dialog);
if (dialog.isCancelled()) {
throw new CancelledException();
}
return (GhidraValuesMap) dialog.getValues();
});
}
/**
* Generates a string key unique to the values defined in the given ValuesMap. Used to store
* and load previously chosen values for the given values map.
* @param valuesMap the ValuesMap to generate a key for
* @return a string that is unique for the values defined in the given ValuesMap
*/
private String generateKey(GValuesMap valuesMap) {
return valuesMap.getValues().stream().map(v -> v.getName()).collect(Collectors.joining());
}
/**
* Returns an int, using the String parameters for guidance. The actual behavior of the
* method depends on your environment, which can be GUI or headless.

View file

@ -18,8 +18,6 @@ package ghidra.app.script;
import java.util.HashMap;
import java.util.Set;
import javax.swing.JOptionPane;
import ghidra.app.events.*;
import ghidra.framework.model.Project;
import ghidra.framework.plugintool.PluginEvent;
@ -29,7 +27,6 @@ import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.Swing;
import ghidra.util.SystemUtilities;
/**
@ -42,7 +39,6 @@ public class GhidraState {
private ProgramSelection currentSelection;
private ProgramSelection currentHighlight;
private HashMap<String, Object> envmap = new HashMap<>();
private GatherParamPanel gatherParamPanel;
private Project project;
private final boolean isGlobalState;
@ -64,11 +60,6 @@ public class GhidraState {
this.currentSelection = selection;
this.currentHighlight = highlight;
this.isGlobalState = true;
if (!SystemUtilities.isInHeadlessMode()) {
Swing.runNow(() -> {
gatherParamPanel = new GatherParamPanel(this);
});
}
}
public GhidraState(GhidraState state) {
@ -115,10 +106,6 @@ public class GhidraState {
return;
}
this.currentProgram = program;
if (gatherParamPanel == null) {
return;
}
gatherParamPanel.currentProgramChanged();
}
/**
@ -252,34 +239,6 @@ public class GhidraState {
return envmap.get(name);
}
public void addParameter(String key, String label, int type, Object defaultValue) {
if (gatherParamPanel == null) {
return;
}
gatherParamPanel.addParameter(key, label, type, defaultValue);
}
public boolean displayParameterGatherer(String title) {
if (gatherParamPanel == null) {
return false;
}
if (!gatherParamPanel.panelShown()) {
int ans = JOptionPane.showConfirmDialog(null, gatherParamPanel, title,
JOptionPane.OK_CANCEL_OPTION);
if (ans == JOptionPane.CANCEL_OPTION) {
gatherParamPanel.setShown(false);
return false;
}
gatherParamPanel.setShown(true);
gatherParamPanel.setParamsInState();
}
return true;
}
public GatherParamPanel getParamPanel() {
return gatherParamPanel;
}
public Set<String> getEnvironmentNames() {
return envmap.keySet();
}

View file

@ -63,7 +63,7 @@ public class SelectLanguageDialog extends DialogComponentProvider {
wasCancelled = true;
}
boolean wasCancelled() {
public boolean wasCancelled() {
return wasCancelled;
}
@ -71,7 +71,7 @@ public class SelectLanguageDialog extends DialogComponentProvider {
return languagePanel.getSelectedLcsPair() != null;
}
void setSelectedLanguage(LanguageCompilerSpecPair language) {
public void setSelectedLanguage(LanguageCompilerSpecPair language) {
Swing.runNow(() -> languagePanel.setSelectedLcsPair(language));
}

View file

@ -122,6 +122,7 @@ public class AddressInput extends JPanel implements FocusableEditor {
/**
* Returns the address in the field or null if the address can't
* be parsed.
* @return The address for the current value in the text field
*
* @throws NullPointerException if AddressFactory has not been set.
*/
@ -160,6 +161,14 @@ public class AddressInput extends JPanel implements FocusableEditor {
return textField.getText().length() != 0;
}
/**
* Returns the text in this field.
* @return the text in this field
*/
public String getText() {
return textField.getText();
}
public AddressFactory getAddressFactory() {
return addrFactory;
}

View file

@ -0,0 +1,104 @@
/* ###
* 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 javax.swing.JComponent;
import docking.widgets.values.*;
import ghidra.app.util.AddressInput;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.listing.Program;
/**
* Value class for {@link Address} types. In order to parse and create Address types, an
* {@link AddressFactory} is required when defining this type. As a convenience, it can
* be constructed with a {@link Program}, in which case it will use the AddressFactory from
* that 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 AddressValue extends AbstractValue<Address> {
private AddressInput field;
private AddressFactory addressFactory;
/**
* Creates an AddressValue with an optional default value and uses the {@link AddressFactory}
* from the given program.
* @param name the name of this value
* @param defaultValue an optional default value
* @param program the program whose AddressFactory will be used to create Addresses.
*/
public AddressValue(String name, Address defaultValue, Program program) {
this(name, defaultValue, program.getAddressFactory());
}
/**
* Creates an AddressValue with an optional default value.
* @param name the name of this value
* @param defaultValue an optional default value
* @param factory the AddressFactory that will be used to create Addresses.
*/
public AddressValue(String name, Address defaultValue, AddressFactory factory) {
super(name, defaultValue);
this.addressFactory = factory;
}
@Override
public JComponent getComponent() {
if (field == null) {
field = new AddressInput();
field.setAddressFactory(addressFactory);
}
return field;
}
@Override
protected void updateValueFromComponent() throws ValuesMapParseException {
Address address = field.getAddress();
if (address == null && field.hasInput()) {
throw new ValuesMapParseException(getName(), "Address",
"Could not parse \"" + field.getText() + "\".");
}
setValue(address);
}
@Override
protected void updateComponentFromValue() {
Address v = getValue();
if (v == null) {
field.clear();
}
else {
field.setAddress(v);
}
}
@Override
protected Address fromString(String valueString) {
Address address = addressFactory.getAddress(valueString);
if (address == null) {
throw new IllegalArgumentException("Invalid address string: " + valueString);
}
return address;
}
}

View file

@ -0,0 +1,288 @@
/* ###
* 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 docking.Tool;
import docking.widgets.values.GValuesMap;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.listing.Program;
/**
* Extends GValuesMap to add Ghidra specific types such as Address and Program
*/
public class GhidraValuesMap extends GValuesMap {
//==================================================================================================
// Define Value Methods
//==================================================================================================
/**
* Defines a value of type {@link Address} with no default value.
* @param name the name for this value
* @param program the program used to get an {@link AddressFactory} for parsing addresses
* @return the new AddressValue that was defined.
*/
public AddressValue defineAddress(String name, Program program) {
checkDup(name);
AddressValue value = new AddressValue(name, null, program);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type {@link Address}
* @param name the name for this value
* @param defaultValue an option default value
* @param program the program used to get an {@link AddressFactory} for parsing addresses
* @return the new AddressValue that was defined.
*/
public AddressValue defineAddress(String name, Address defaultValue, Program program) {
checkDup(name);
AddressValue value = new AddressValue(name, defaultValue, program);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type {@link Address}
* @param name the name for this value
* @param defaultValue an option default value
* @param factory the {@link AddressFactory} used to parse addresses
* @return the new AddressValue that was defined.
*/
public AddressValue defineAddress(String name, Address defaultValue, AddressFactory factory) {
checkDup(name);
AddressValue value = new AddressValue(name, defaultValue, factory);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type LanguageCompilerSpecPair (folders in a Ghidra project).
* @param name the name for this value
* @param defaultValue the initial value (can be null)
* @return the new ProjectFolderValue that was defined
*/
public LanguageValue defineLanguage(String name, LanguageCompilerSpecPair defaultValue) {
checkDup(name);
LanguageValue value = new LanguageValue(name, defaultValue);
valuesMap.put(name, value);
return value;
}
/**
* 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.
* @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.
*/
public ProgramValue defineProgram(String name, Object consumer, Tool tool) {
return defineProgram(name, null, consumer, tool);
}
/**
* Defines a value of type Program.
* @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
*/
public ProgramValue defineProgram(String name, Program defaultValue, Object consumer,
Tool tool) {
checkDup(name);
ProgramValue value = new ProgramValue(name, defaultValue, consumer, tool);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type DomainFile (files in a Ghidra project).
* @param name the name for this value
* @return the new ProjectFileValue that was defined
*/
public ProjectFileValue defineProjectFile(String name) {
return defineProjectFile(name, null);
}
/**
* Defines a value of type DomainFile (files in a Ghidra project).
* @param name the name for this value
* @param defaultValue the initial value
* @return the new ProjectFileValue that was defined
*/
public ProjectFileValue defineProjectFile(String name, DomainFile defaultValue) {
checkDup(name);
ProjectFileValue value = new ProjectFileValue(name, defaultValue);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type DomainFolder (folders in a Ghidra project).
* @param name the name for this value
* @return the new ProjectFolderValue that was defined
*/
public ProjectFolderValue defineProjectFolder(String name) {
return defineProjectFolder(name, null);
}
/**
* 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)
* @return the new ProjectFolderValue that was defined
*/
public ProjectFolderValue defineProjectFolder(String name, DomainFolder defaultValue) {
checkDup(name);
ProjectFolderValue value = new ProjectFolderValue(name, defaultValue);
valuesMap.put(name, value);
return value;
}
/**
* Gets the {@link Address} value for the given name.
* @param name the name of a previously defined Address value
* @return the Address
* @throws IllegalArgumentException if the name hasn't been defined as an Address type
*/
public Address getAddress(String name) {
AddressValue addressValue = getValue(name, AddressValue.class, "Address");
return addressValue.getValue();
}
//==================================================================================================
// Get Value Methods
//==================================================================================================
/**
* Gets the Language ({@link LanguageCompilerSpecPair}) value for the given name.
* @param name the name of a previously defined language value
* @return the language value
* @throws IllegalArgumentException if the name hasn't been defined as a language type
*/
public LanguageCompilerSpecPair getLanguage(String name) {
LanguageValue value = getValue(name, LanguageValue.class, "Language");
return value.getValue();
}
/**
* Gets the {@link Program} value for the given name.
* @param name the name of a previously defined project folder value
* @return the project folder value
* @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();
}
/**
* Gets the project file ({@link DomainFile}) value for the given name.
* @param name the name of a previously defined project file value
* @return the project file value
* @throws IllegalArgumentException if the name hasn't been defined as a project file type
*/
public DomainFile getProjectFile(String name) {
ProjectFileValue domainFileValue = getValue(name, ProjectFileValue.class, "Domain File");
return domainFileValue.getValue();
}
/**
* Gets the project folder ({@link DomainFolder}) value for the given name.
* @param name the name of a previously defined project folder value
* @return the project folder value
* @throws IllegalArgumentException if the name hasn't been defined as a project folder type
*/
public DomainFolder getProjectFolder(String name) {
ProjectFolderValue domainFolderValue =
getValue(name, ProjectFolderValue.class, "Domain Folder");
return domainFolderValue.getValue();
}
//==================================================================================================
// Set Value Methods
//==================================================================================================
/**
* Sets the address value for the given name.
* @param name the name of the Address value that was previously defined
* @param address the address to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as an Address type
*/
public void setAddress(String name, Address address) {
AddressValue addressValue = getValue(name, AddressValue.class, "Address");
addressValue.setValue(address);
}
/**
* Sets the Language ({@link LanguageCompilerSpecPair}) value for the given name.
* @param name the name of the Language value that was previously defined
* @param value the Language to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a Language type
*/
public void setLanguage(String name, LanguageCompilerSpecPair value) {
LanguageValue languageValue = getValue(name, LanguageValue.class, "Language");
languageValue.setValue(value);
}
/**
* Sets the {@link Program} value for the given name.
* @param name the name of the Program value that was previously defined
* @param program the Program to set as the value
* @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);
}
/**
* Sets the project file {@link DomainFile} value for the given name.
* @param name the name of the project file value that was previously defined
* @param file the project file to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a project file type
*/
public void setProjectFile(String name, DomainFile file) {
ProjectFileValue domainFileValue = getValue(name, ProjectFileValue.class, "Domain File");
domainFileValue.setValue(file);
}
/**
* Sets the project folder {@link DomainFolder} value for the given name.
* @param name the name of the project folder value that was previously defined
* @param folder the project folder to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a project folder type
*/
public void setProjectFolder(String name, DomainFolder folder) {
ProjectFolderValue domainFolderValue =
getValue(name, ProjectFolderValue.class, "Domain Folder");
domainFolderValue.setValue(folder);
}
}

View file

@ -0,0 +1,195 @@
/* ###
* 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.awt.BorderLayout;
import java.io.File;
import java.util.*;
import javax.swing.*;
import docking.widgets.button.BrowseButton;
import docking.widgets.values.*;
import ghidra.app.script.SelectLanguageDialog;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
/**
* Value class for LanguageCompilerSpecPair types. The component for this class is a
* TextField with a browse button for bringing up a language/compiler chooser. It supports
* the concept of no value when the text field is empty. If it is not empty, the the contents
* must be one of the known valid language/compiler spec pairs.
* <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 LanguageValue extends AbstractValue<LanguageCompilerSpecPair> {
private LangaugeValuePanel languagePanel;
/**
* Construct a new LanguageVlue with no value
* @param name the name of the value
*/
public LanguageValue(String name) {
super(name, null);
}
/**
* Construct a new LanguageVlue with a given optional default value.
* @param name the name of the value
* @param defaultValue the optional default value
*/
public LanguageValue(String name, LanguageCompilerSpecPair defaultValue) {
super(name, defaultValue);
}
@Override
public JComponent getComponent() {
if (languagePanel == null) {
languagePanel = new LangaugeValuePanel(getName());
}
return languagePanel;
}
@Override
protected void updateValueFromComponent() throws ValuesMapParseException {
setValue(languagePanel.getLanguage());
}
@Override
protected void updateComponentFromValue() {
languagePanel.setLanguage(getValue());
}
@Override
public LanguageCompilerSpecPair fromString(String valueString) {
try {
return parseLanguageCompileSpecPair(valueString);
}
catch (ValuesMapParseException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
/**
* Parses a LanguageCompilerSpecPair from a string.
*
* @param val 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
*/
public LanguageCompilerSpecPair parseLanguageCompileSpecPair(String val)
throws ValuesMapParseException {
if (val.isBlank()) {
return null;
}
// Split on last colon to get separated languageID and compilerSpecID
int lastColon = val.lastIndexOf(':');
if (lastColon < 1) {
throw new ValuesMapParseException(getName(), "Language/Compiler Spec",
"Could not parse \"" + val + "\".");
}
Set<LanguageCompilerSpecPair> languages = getLanguagesCompilerPairs();
String langId = val.substring(0, lastColon);
String compilerId = val.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 + "\"");
}
return storedLCS;
}
private Set<LanguageCompilerSpecPair> getLanguagesCompilerPairs() {
Set<LanguageCompilerSpecPair> languages = new HashSet<>();
LanguageService languageService = DefaultLanguageService.getLanguageService();
List<LanguageDescription> descriptions = languageService.getLanguageDescriptions(false);
for (LanguageDescription description : descriptions) {
Collection<CompilerSpecDescription> csDescriptions =
description.getCompatibleCompilerSpecDescriptions();
for (CompilerSpecDescription csDescription : csDescriptions) {
languages.add(new LanguageCompilerSpecPair(description.getLanguageID(),
csDescription.getCompilerSpecID()));
}
}
return languages;
}
class LangaugeValuePanel extends JPanel {
private JTextField textField;
private JButton browseButton;
public LangaugeValuePanel(String name) {
super(new BorderLayout());
setName(name);
textField = new JTextField(20);
browseButton = new BrowseButton();
browseButton.addActionListener(e -> showLanguageDialog());
add(textField, BorderLayout.CENTER);
add(browseButton, BorderLayout.EAST);
}
public LanguageCompilerSpecPair getLanguage() throws ValuesMapParseException {
return parseLanguageCompileSpecPair(textField.getText());
}
public void setLanguage(LanguageCompilerSpecPair value) {
String text = value == null ? "" : value.toString();
textField.setText(text);
}
private void showLanguageDialog() {
SelectLanguageDialog dialog = new SelectLanguageDialog("Select Language", "Ok");
try {
dialog.setSelectedLanguage(getLanguage());
}
catch (ValuesMapParseException e) {
// we are just trying to initialize dialog, so don't care at this time
}
dialog.show();
LanguageCompilerSpecPair selectedLanguage = dialog.getSelectedLanguage();
if (selectedLanguage != null) {
textField.setText(selectedLanguage.toString());
}
dialog.dispose();
}
public File getFile() {
String text = textField.getText().trim();
if (text.isBlank()) {
return null;
}
return new File(text);
}
public void setText(String val) {
textField.setText(val);
}
}
}

View file

@ -0,0 +1,153 @@
/* ###
* 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

@ -0,0 +1,117 @@
/* ###
* 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.awt.BorderLayout;
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
*/
class ProjectBrowserPanel extends JPanel {
private JTextField textField;
private JButton browseButton;
private boolean selectFolders;
ProjectBrowserPanel(String name, boolean selectFolders) {
super(new BorderLayout());
this.selectFolders = selectFolders;
setName(name);
textField = new JTextField(20);
browseButton = new BrowseButton();
browseButton.addActionListener(e -> showDomainFileChooser());
add(textField, BorderLayout.CENTER);
add(browseButton, BorderLayout.EAST);
}
void setDomainFile(DomainFile value) {
String text = value == null ? "" : value.getPathname();
textField.setText(text);
}
void setDomainFolder(DomainFolder value) {
String text = value == null ? "" : value.getPathname();
textField.setText(text);
}
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();
textField.setText(text);
dialog.dispose();
}
DomainFile getDomainFile() {
String text = textField.getText().trim();
if (text.isBlank()) {
return null;
}
return parseDomainFile(text);
}
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) {
// 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);
if (df != null) {
return df;
}
return null;
}
static DomainFolder parseDomainFolder(String path) {
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);
if (df != null) {
return df;
}
return null;
}
}

View file

@ -0,0 +1,113 @@
/* ###
* 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 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.Project;
import ghidra.framework.store.FileSystem;
/**
* Value class for project files ({@link DomainFile}). The editor component consists of the
* {@link JTextField} and a browse button for bringing up a {@link DataTreeDialog} for picking
* project files 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 ProjectFileValue extends AbstractValue<DomainFile> {
private ProjectBrowserPanel domainFilePanel;
public ProjectFileValue(String name) {
this(name, null);
}
public ProjectFileValue(String name, DomainFile defaultValue) {
super(name, defaultValue);
}
@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(), "Project File",
"No file found for \"" + text + "\"");
}
setValue(domainFile);
}
}
@Override
protected void updateComponentFromValue() {
if (domainFilePanel != null) {
domainFilePanel.setDomainFile(getValue());
}
}
@Override
protected DomainFile fromString(String valueString) {
DomainFile df = parseDomainFile(valueString);
if (df == null) {
throw new IllegalArgumentException("Can't find domain file: " + valueString);
}
return df;
}
private DomainFile parseDomainFile(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();
if (activeProject == null) {
throw new IllegalStateException("No Active Project!");
}
DomainFile df = activeProject.getProjectData().getFile(val);
if (df != null) {
return df;
}
return null;
}
@Override
protected String toString(DomainFile v) {
return v.getPathname();
}
}

View file

@ -0,0 +1,98 @@
/* ###
* 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 javax.swing.JComponent;
import javax.swing.JTextField;
import docking.widgets.values.*;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
/**
* Value class for project folders ({@link DomainFile}). The editor component consists of the
* {@link JTextField} and a browse button for bringing up a {@link DataTreeDialog} for picking
* project folders 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 ProjectFolderValue extends AbstractValue<DomainFolder> {
private ProjectBrowserPanel domainFilePanel;
public ProjectFolderValue(String name) {
this(name, null);
}
public ProjectFolderValue(String name, DomainFolder defaultValue) {
super(name, defaultValue);
}
@Override
public JComponent getComponent() {
if (domainFilePanel == null) {
domainFilePanel = new ProjectBrowserPanel(getName(), true);
}
return domainFilePanel;
}
@Override
protected void updateValueFromComponent() throws ValuesMapParseException {
if (domainFilePanel != null) {
DomainFolder domainFolder = domainFilePanel.getDomainFolder();
if (domainFolder == null) {
String text = domainFilePanel.getText();
if (text.isBlank()) {
setValue(null);
return;
}
throw new ValuesMapParseException(getName(), "Project Folder",
"No folder found for \"" + text + "\"");
}
setValue(domainFolder);
}
}
@Override
protected void updateComponentFromValue() {
if (domainFilePanel != null) {
domainFilePanel.setDomainFolder(getValue());
}
}
@Override
protected DomainFolder fromString(String valueString) {
DomainFolder df = ProjectBrowserPanel.parseDomainFolder(valueString);
if (df == null) {
throw new IllegalArgumentException("Can't find domain folder: " + valueString);
}
return df;
}
@Override
protected String toString(DomainFolder v) {
return v.getPathname();
}
}

View file

@ -435,6 +435,14 @@ public class DataTreeDialog extends DialogComponentProvider
Swing.runLater(() -> treePanel.selectRootDataFolder());
}
/**
* Select a folder in the tree.
* @param folder the folder to select
*/
public void selectFolder(DomainFolder folder) {
Swing.runLater(() -> treePanel.selectDomainFolder(folder));
}
/**
* Select the node that corresponds to the given domain file.
* @param file the file

View file

@ -299,6 +299,9 @@ public class NewLanguagePanel extends JPanel {
}
public boolean setSelectedLcsPair(LanguageCompilerSpecPair lcsPair) {
if (lcsPair == null) {
return false;
}
int index = tableModel.getFirstLcsPairIndex(lcsPair);
if (index == -1) {
return false;

View file

@ -0,0 +1,132 @@
/* ###
* 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 javax.swing.JButton;
import javax.swing.JTextField;
import org.junit.After;
import org.junit.Before;
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.framework.main.DataTreeDialog;
import ghidra.framework.model.*;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractValueIntegrationTest extends AbstractGhidraHeadedIntegrationTest {
protected ValuesMapDialog dialog;
protected GhidraValuesMap values = new GhidraValuesMap();
protected TestEnv env;
protected DomainFolder rootFolder;
protected DomainFolder folder;
protected DomainFile fileA;
protected DomainFile fileB;
protected ProgramDB programA;
protected ProgramDB programB;
@Before
public void setup() throws Exception {
env = new TestEnv();
Project project = env.getProject();
runSwing(() -> env.getFrontEndTool().setActiveProject(project));
rootFolder = project.getProjectData().getRootFolder();
folder = rootFolder.createFolder("A");
ProgramBuilder programBuilderA = new ProgramBuilder("A", ProgramBuilder._TOY);
ProgramBuilder programBuilderB = new ProgramBuilder("B", ProgramBuilder._TOY);
programA = programBuilderA.getProgram();
programB = programBuilderB.getProgram();
fileA = folder.createFile("A", programA, TaskMonitor.DUMMY);
fileB = folder.createFile("B", programB, TaskMonitor.DUMMY);
}
@After
public void tearDown() {
env.dispose();
}
protected void showDialogOnSwingWithoutBlocking() {
runSwing(() -> {
dialog = new ValuesMapDialog("Test", null, values);
DockingWindowManager.showDialog(dialog);
}, false);
waitForDialogComponent(DialogComponentProvider.class);
}
protected void pressOk() {
JButton okButton = (JButton) getInstanceField("okButton", dialog);
runSwing(() -> okButton.doClick());
}
protected void pressCancel() {
JButton okButton = (JButton) getInstanceField("cancelButton", dialog);
runSwing(() -> okButton.doClick());
}
protected void setProjectFileOnProjectTree(AbstractValue<?> value, DomainFile file) {
ProjectBrowserPanel projectWidget = (ProjectBrowserPanel) value.getComponent();
pressButtonByName(projectWidget, "BrowseButton", false);
DataTreeDialog dataTreeDialog = waitForDialogComponent(DataTreeDialog.class);
runSwing(() -> {
dataTreeDialog.selectDomainFile(file);
});
waitForSwing();
pressButtonByText(dataTreeDialog, "OK");
}
protected void setProjectFolderOnProjectTree(AbstractValue<?> value, DomainFolder folder) {
ProjectBrowserPanel projectWidget = (ProjectBrowserPanel) value.getComponent();
pressButtonByName(projectWidget, "BrowseButton", false);
DataTreeDialog dataTreeDialog = waitForDialogComponent(DataTreeDialog.class);
runSwing(() -> {
dataTreeDialog.selectFolder(folder);
});
waitForSwing();
pressButtonByText(dataTreeDialog, "OK");
}
protected void setTextOnComponent(AbstractValue<?> nameValue, String text) {
runSwing(() -> {
JTextField field = (JTextField) nameValue.getComponent();
field.setText(text);
});
}
protected AddressFactory createAddressFactory() {
GenericAddressSpace space1 = new GenericAddressSpace("A", 64, AddressSpace.TYPE_RAM, 0);
GenericAddressSpace space2 = new GenericAddressSpace("B", 64, AddressSpace.TYPE_RAM, 0);
return new DefaultAddressFactory(new AddressSpace[] { space1, space2 });
}
}

View file

@ -0,0 +1,150 @@
/* ###
* 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 static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.values.AbstractValue;
import ghidra.app.util.AddressInput;
import ghidra.features.base.values.AddressValue;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory;
public class AddressValueTest extends AbstractValueIntegrationTest {
private static final String NAME = "Start Address";
protected AddressFactory factory = createAddressFactory();
@Test
public void testAddressValueNoDefault() {
values.defineAddress(NAME, null, factory);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setAddress(NAME, addr(13));
assertTrue(values.hasValue(NAME));
assertEquals(addr(13), values.getAddress(NAME));
}
@Test
public void testAddressValueWithDefault() {
values.defineAddress(NAME, addr(1), factory);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(addr(1), values.getAddress(NAME));
values.setAddress(NAME, addr(2));
assertTrue(values.hasValue(NAME));
assertEquals(addr(2), values.getAddress(NAME));
values.setAddress(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testGetAsText() {
AddressValue value1 = new AddressValue(NAME, addr(0x123), factory);
AddressValue value2 = new AddressValue(NAME, null, factory);
assertEquals("A:00000123", value1.getAsText());
assertNull(value2.getAsText());
}
@Test
public void testSetAsText() {
AddressValue v = new AddressValue(NAME, null, factory);
assertEquals(addr(0x123), v.setAsText("A:00000123"));
try {
v.setAsText("xdsf");
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
try {
v.setAsText(null);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineAddress(NAME, null, factory);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getAddress(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineAddress(NAME, null, factory);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnAddressInput(values.getAbstractValue(NAME), "2");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(addr(2), values.getAddress(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineAddress(NAME, addr(1), factory);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(addr(1), values.getAddress(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineAddress(NAME, addr(1), factory);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnAddressInput(values.getAbstractValue(NAME), "2");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(addr(2), values.getAddress(NAME));
}
private Address addr(int offset) {
return factory.getDefaultAddressSpace().getAddress(offset);
}
protected void setTextOnAddressInput(AbstractValue<?> nameValue, String text) {
runSwing(() -> {
AddressInput addressInput = (AddressInput) nameValue.getComponent();
addressInput.setValue(text);
});
}
}

View file

@ -0,0 +1,167 @@
/* ###
* 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 static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.values.AbstractValue;
import ghidra.app.script.SelectLanguageDialog;
import ghidra.features.base.values.LanguageValue;
import ghidra.features.base.values.LanguageValue.LangaugeValuePanel;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
public class LanguageValueTest extends AbstractValueIntegrationTest {
private static final String NAME = "Lang";
private static final LanguageCompilerSpecPair LANG1 =
new LanguageCompilerSpecPair("6502:LE:16:default", "default");
private static final LanguageCompilerSpecPair LANG2 =
new LanguageCompilerSpecPair("ARM:BE:32:v7", "default");
@Test
public void testLanguageValueNoDefault() {
values.defineLanguage(NAME, null);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setLanguage(NAME, LANG1);
assertTrue(values.hasValue(NAME));
assertEquals(LANG1, values.getLanguage(NAME));
}
@Test
public void testLanguageValueWithDefault() {
values.defineLanguage(NAME, LANG1);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(LANG1, values.getLanguage(NAME));
values.setLanguage(NAME, LANG2);
assertTrue(values.hasValue(NAME));
assertEquals(LANG2, values.getLanguage(NAME));
values.setLanguage(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testGetAsText() {
LanguageValue value1 = new LanguageValue(NAME);
LanguageValue value2 = new LanguageValue(NAME, LANG1);
assertNull(value1.getAsText());
assertEquals("6502:LE:16:default:default", value2.getAsText());
}
@Test
public void testSetAsText() {
LanguageValue v = new LanguageValue(NAME);
assertEquals(LANG1, v.setAsText("6502:LE:16:default:default"));
try {
v.setAsText(null);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineLanguage(NAME, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getLanguage(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineLanguage(NAME, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setLanguage(values.getAbstractValue(NAME), LANG1);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(LANG1, values.getLanguage(NAME));
}
@Test
public void testNoDefaultValueWithBadDialogInput() {
values.defineLanguage(NAME, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setLanguage(values.getAbstractValue(NAME), "asdfa");
pressOk();
assertTrue(dialog.isShowing());
pressCancel();
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineLanguage(NAME, LANG1);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(LANG1, values.getLanguage(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineLanguage(NAME, LANG1);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setLanguage(values.getAbstractValue(NAME), LANG2);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(LANG2, values.getLanguage(NAME));
}
protected void setLanguage(AbstractValue<?> nameValue, LanguageCompilerSpecPair lang) {
LangaugeValuePanel languageWidget = (LangaugeValuePanel) nameValue.getComponent();
pressButtonByName(languageWidget, "BrowseButton", false);
SelectLanguageDialog langDialog = waitForDialogComponent(SelectLanguageDialog.class);
runSwing(() -> {
langDialog.setSelectedLanguage(lang);
});
pressButtonByText(langDialog, "Ok");
}
protected void setLanguage(AbstractValue<?> nameValue, String val) {
LangaugeValuePanel languageWidget = (LangaugeValuePanel) nameValue.getComponent();
runSwing(() -> {
languageWidget.setText(val);
});
}
}

View file

@ -0,0 +1,155 @@
/* ###
* 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 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 {
private static final String NAME = "Program";
@Test
public void testProgramValueNoDefault() {
values.defineProgram(NAME, this, null);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setProgram(NAME, programA);
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME));
}
@Test
public void testProgramValueWithDefault() {
values.defineProgram(NAME, programA, this, null);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME));
values.setProgram(NAME, programB);
assertTrue(values.hasValue(NAME));
assertEquals(programB, values.getProgram(NAME));
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());
}
@Test
public void testSetAsText() {
ProgramValue v = new ProgramValue(NAME, this, null);
assertEquals(programA, v.setAsText("/A/A"));
try {
v.setAsText(null);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
try {
v.setAsText("/z/z/t");
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineProgram(NAME, this, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getProgram(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineProgram(NAME, this, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineProgram(NAME, programA, this, null);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(programA, values.getProgram(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineProgram(NAME, programA, this, null);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programB.getDomainFile());
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(programB, values.getProgram(NAME));
}
@Test
public void testOpenProgramInTool() {
PluginTool tool = env.createDefaultTool();
ProgramManager programManagerService = tool.getService(ProgramManager.class);
Program[] allOpenPrograms = programManagerService.getAllOpenPrograms();
assertEquals(0, allOpenPrograms.length);
values.defineProgram(NAME, this, tool);
showDialogOnSwingWithoutBlocking();
setProjectFileOnProjectTree(values.getAbstractValue(NAME), programA.getDomainFile());
pressOk();
allOpenPrograms = programManagerService.getAllOpenPrograms();
assertEquals(1, allOpenPrograms.length);
assertEquals(programA, allOpenPrograms[0]);
}
}

View file

@ -0,0 +1,135 @@
/* ###
* 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 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";
@Test
public void testProjectFileValueNoDefault() {
values.defineProjectFile(NAME, null);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setProjectFile(NAME, fileA);
assertTrue(values.hasValue(NAME));
assertEquals(fileA, values.getProjectFile(NAME));
}
@Test
public void testProjectFileValueWithDefault() {
values.defineProjectFile(NAME, fileA);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(fileA, values.getProjectFile(NAME));
values.setProjectFile(NAME, fileB);
assertTrue(values.hasValue(NAME));
assertEquals(fileB, values.getProjectFile(NAME));
values.setProjectFile(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testGetAsText() {
ProjectFileValue value1 = new ProjectFileValue(NAME);
ProjectFileValue value2 = new ProjectFileValue(NAME, fileA);
assertNull(value1.getAsText());
assertEquals("/A/A", value2.getAsText());
}
@Test
public void testSetAsText() {
ProjectFileValue v = new ProjectFileValue(NAME);
assertEquals(fileA, v.setAsText("/A/A"));
try {
v.setAsText(null);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
try {
v.setAsText("/zasd/asdfas");
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineProjectFile(NAME, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getProjectFile(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineProjectFile(NAME, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setProjectFileOnProjectTree(values.getAbstractValue(NAME), fileA);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(fileA, values.getProjectFile(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineProjectFile(NAME, fileA);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(fileA, values.getProjectFile(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineProjectFile(NAME, fileA);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setProjectFileOnProjectTree(values.getAbstractValue(NAME), fileB);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(fileB, values.getProjectFile(NAME));
}
}

View file

@ -0,0 +1,137 @@
/* ###
* 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 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";
@Test
public void testProjectFolderValueNoDefault() {
values.defineProjectFolder(NAME, null);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setProjectFolder(NAME, folder);
assertTrue(values.hasValue(NAME));
assertEquals(folder, values.getProjectFolder(NAME));
}
@Test
public void testProjectFolderValueWithDefault() {
values.defineProjectFolder(NAME, rootFolder);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(rootFolder, values.getProjectFolder(NAME));
values.setProjectFolder(NAME, folder);
assertTrue(values.hasValue(NAME));
assertEquals(folder, values.getProjectFolder(NAME));
values.setProjectFolder(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testGetAsText() {
ProjectFolderValue value1 = new ProjectFolderValue(NAME);
ProjectFolderValue value2 = new ProjectFolderValue(NAME, folder);
assertNull(value1.getAsText());
assertEquals("/A", value2.getAsText());
}
@Test
public void testSetAsText() {
ProjectFolderValue v = new ProjectFolderValue(NAME);
assertEquals(folder, v.setAsText("/A"));
try {
v.setAsText(null);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
try {
v.setAsText("/zasd/asdfas");
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineProjectFolder(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
// usually, this would have no value, but the root folder is such an obvious value
// in nothing is entered, we use that.
assertTrue(values.hasValue(NAME));
assertEquals(rootFolder, values.getProjectFolder(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineProjectFolder(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setProjectFolderOnProjectTree(values.getAbstractValue(NAME), folder);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(folder, values.getProjectFolder(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineProjectFolder(NAME, folder);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(folder, values.getProjectFolder(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineProjectFolder(NAME, rootFolder);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setProjectFolderOnProjectTree(values.getAbstractValue(NAME), folder);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(folder, values.getProjectFolder(NAME));
}
}

View file

@ -0,0 +1,49 @@
/* ###
* 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 static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.values.AbstractValue;
import ghidra.app.util.AddressInput;
public class ValuesMapDialogParseErrorTest extends AbstractValueIntegrationTest {
@Test
public void testParseErrorBlocksDialogFromClosing() {
values.defineString("Name");
values.defineAddress("Start", programA);
values.defineInt("Size");
showDialogOnSwingWithoutBlocking();
setTextOnAddressInput(values.getAbstractValue("Start"), "sdfasf");
pressOk();
assertTrue(dialog.isShowing());
assertTrue(dialog.getStatusText().startsWith("Error"));
setTextOnAddressInput(values.getAbstractValue("Start"), "0");
pressOk();
assertFalse(dialog.isShowing());
}
protected void setTextOnAddressInput(AbstractValue<?> nameValue, String text) {
runSwing(() -> {
AddressInput addressInput = (AddressInput) nameValue.getComponent();
addressInput.setValue(text);
});
}
}

View file

@ -222,7 +222,7 @@ public class ScrollableTextArea extends JScrollPane {
* used by all constructors to finish initialization of the object
*/
private void initialize() {
textArea.setLineWrap(false);
textArea.setLineWrap(true);
this.setAutoscrolls(true);
this.setViewportView(textArea);

View file

@ -0,0 +1,150 @@
/* ###
* 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 docking.widgets.values;
import java.util.Objects;
import javax.swing.JComponent;
/**
* Abstract base class for defined name/values in a {@link GValuesMap} and whose values can be
* edited in the {@link ValuesMapDialog}. Its main purpose is to provide a JComponent for
* editing the value. Generally, objects of this type can be in one of two states: having a value
* or not. This can be useful for validating the dialog input values to ensure the user enters
* a value.
* <P>
* There are two situations where parse/conversion exceptions can occur in subclass implementations.
* One is the {@link #setAsText(String)} method. The subclass should catch any specific expected
* exception when parsing the string and convert it to an IllegalArgumentException. The other method
* is the {@link #updateValueFromComponent()} method which may also need to parse string data. In
* this case any expected exception should be converted to {@link ValuesMapParseException}. This
* is the only exception type the dialog will be trapping and displaying error messages for in the
* {@link ValuesMapDialog}. Any other type of exception will be considered unexpected and a
* programing error and will be eventally be handled by the default application error handler.
*
* @param <T> The type of the value stored and edited by this class
*/
public abstract class AbstractValue<T> {
private final String name;
private T value;
/**
* Constructor that assigned a name and optional initial value for this object.
* @param name the name associated with this value.
* @param defaultValue an optional initial value for this object
*/
protected AbstractValue(String name, T defaultValue) {
this.name = Objects.requireNonNull(name);
this.value = defaultValue;
}
/**
* Returns the name of this value object.
* @return the name of this value object
*/
public String getName() {
return name;
}
/**
* Returns the value currently assigned to this object.
* @return the value currently assigned to this object (may be null)
*/
public T getValue() {
return value;
}
/**
* Sets the value for this object.
* @param value the value to set for this object (may be null)
*/
public void setValue(T value) {
this.value = value;
}
/**
* Copies the T value from the given AbstractValue to this AbstractValue.
* @param other the AbstractValue to copy from
*/
public void copyValue(AbstractValue<T> other) {
setValue(other.getValue());
}
/**
* Returns true if the value is non-null.
* @return true if the value is non-null
*/
public boolean hasValue() {
return value != null;
}
/**
* Sets the value for this object from the given string. If this object can not succesfully
* parse the string, an exception will be thrown.
* @param valueString the string to be parsed into the type for this object
* @return The value resulting from parsing the string value
* @throws IllegalArgumentException if the string can not be parsed into a value of type T
*/
public T setAsText(String valueString) {
if (valueString == null) {
throw new IllegalArgumentException("Value string can not be null!");
}
value = fromString(valueString);
return value;
}
/**
* Returns a string representation for the value. It is expected that the string returned
* from this method can be parsed by the corresponding {@link #setAsText(String)} method. If the
* value of this object is null, null will be returned.
* @return a string representation for the value or null if the value is null
*/
public String getAsText() {
return value == null ? null : toString(value);
}
protected String toString(T t) {
return t.toString();
}
/**
* Returns a JComponent for entering or editing a value of this type.
* @return a JComponent for entering or editing a value of this type.
*/
public abstract JComponent getComponent();
/**
* Causes the stored value for this object to be updated based on the state of the
* JComponent returned from {@link #getComponent()}
* @throws ValuesMapParseException if an error occurs trying update the value from a
* component. This usually is a result of trying to parse a string value.
*/
protected abstract void updateValueFromComponent() throws ValuesMapParseException;
/**
* Updates the JComponent returned from {@link #getComponent()} to represent the current
* value of this object.
*/
protected abstract void updateComponentFromValue();
/**
* Parses the given string into a value of type T
* @param valueString the string to parse
* @return a value of type T
*/
protected abstract T fromString(String valueString);
}

View file

@ -0,0 +1,63 @@
/* ###
* 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 docking.widgets.values;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
/**
* Value class for {@link Boolean} types. Boolean types use a {@link JCheckBox} for displaying and
* modifying values. Because the checkBox is always either checked or unchecked,
* BooleanValues don't support the concept of having no value.
* <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 BooleanValue extends AbstractValue<Boolean> {
private JCheckBox checkBox;
BooleanValue(String name, boolean defaultValue) {
super(name, defaultValue);
}
@Override
public JComponent getComponent() {
if (checkBox == null) {
checkBox = new JCheckBox();
}
return checkBox;
}
@Override
protected void updateValueFromComponent() {
setValue(checkBox.isSelected());
}
@Override
protected void updateComponentFromValue() {
checkBox.setSelected(getValue());
}
@Override
protected Boolean fromString(String valueString) {
return Boolean.parseBoolean(valueString);
}
}

View file

@ -0,0 +1,82 @@
/* ###
* 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 docking.widgets.values;
import javax.swing.JComponent;
import docking.widgets.combobox.GComboBox;
/**
* Value class for selecting from a restricted set of {@link String}s. ChoiceValues uses a
* {@link GComboBox} for the editor component.
* <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 ChoiceValue extends AbstractValue<String> {
private String[] choices;
private GComboBox<String> combo;
ChoiceValue(String name, String defaultValue, String... choices) {
super(name, defaultValue);
this.choices = choices;
if (defaultValue != null && !isValidChoice(defaultValue)) {
throw new IllegalArgumentException("Default value is not one of the valid choices!");
}
}
@Override
public JComponent getComponent() {
if (combo == null) {
combo = new GComboBox<String>(choices);
}
return combo;
}
@Override
protected void updateValueFromComponent() {
setValue((String) combo.getSelectedItem());
}
@Override
protected void updateComponentFromValue() {
combo.setSelectedItem(getValue());
}
@Override
protected String fromString(String valueString) {
if (valueString == null) {
return null;
}
if (isValidChoice(valueString)) {
return valueString;
}
throw new IllegalArgumentException(valueString + " is not a valid choice!");
}
private boolean isValidChoice(String valueString) {
for (String choice : choices) {
if (choice.equals(valueString)) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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 docking.widgets.values;
import javax.swing.JComponent;
import docking.widgets.textfield.FloatingPointTextField;
/**
* Value class for {@link Double} types. This value uses a {@link FloatingPointTextField} as it's
* editor component. It supports the concept of no value, if the text field is empty.
* <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 DoubleValue extends AbstractValue<Double> {
private FloatingPointTextField field;
public DoubleValue(String name) {
this(name, null);
}
public DoubleValue(String name, Double defaultValue) {
super(name, defaultValue);
}
@Override
public JComponent getComponent() {
if (field == null) {
field = new FloatingPointTextField(20);
}
return field;
}
@Override
protected void updateValueFromComponent() {
String text = field.getText();
// special case where user didn't enter a value on a string field that was defined without
// a value
if (getValue() == null && text.equals("")) {
return;
}
setValue(field.getValue());
}
@Override
protected void updateComponentFromValue() {
Double value = getValue();
if (value == null) {
field.setText("");
return;
}
field.setValue(value);
}
@Override
public Double fromString(String valueString) {
return Double.parseDouble(valueString);
}
}

View file

@ -0,0 +1,146 @@
/* ###
* 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 docking.widgets.values;
import java.awt.BorderLayout;
import java.io.File;
import javax.swing.*;
import docking.widgets.button.BrowseButton;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
/**
* Value class for {@link File} types. FileValues can be used for either file or directory values,
* depending on the constructor options. The editor component uses a {@link JTextField} with
* a browse button for bringing up a {@link GhidraFileChooser} for picking files or directories.
* <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 FileValue extends AbstractValue<File> {
private GhidraFileChooserMode chooserMode;
private File startingDir;
private FileValuePanel filePanel;
/**
* Constructs a FileValue that expects its value to represent a file and not a directory.
* @param name the name of the value
*/
public FileValue(String name) {
this(name, null);
}
/**
* Constructs a FileValue that expects its value to represent a file and not a directory.
* @param name the name of the value
* @param defaultValue the optional default File value.
*/
public FileValue(String name, File defaultValue) {
this(name, defaultValue, null, GhidraFileChooserMode.FILES_AND_DIRECTORIES);
}
/**
* Constructs a FileValue that could represent either a File or Directory, depending on the
* mode value.
* @param name the name of the value
* @param defaultValue the optional default File value. If non-null this can be either a
* file or directory, but it should match the given {@link GhidraFileChooserMode}
* @param startingDir an optional directory specifying where the FileChooser should intialize
* its starting selected directory.
* @param mode the {@link GhidraFileChooserMode} used to indicate if this File represents a
* file or directory. It will put the GhidraFileChooser in a mode for choosing files or
* directories.
*/
public FileValue(String name, File defaultValue, File startingDir, GhidraFileChooserMode mode) {
super(name, defaultValue);
this.chooserMode = mode;
this.startingDir = startingDir;
}
@Override
public JComponent getComponent() {
if (filePanel == null) {
filePanel = new FileValuePanel(getName());
}
return filePanel;
}
@Override
public void updateValueFromComponent() {
setValue(filePanel.getFile());
}
@Override
public void updateComponentFromValue() {
filePanel.setValue(getValue());
}
@Override
public File fromString(String valueString) {
return new File(valueString);
}
// not private so that tests can access this class
class FileValuePanel extends JPanel {
private JTextField textField;
private JButton browseButton;
public FileValuePanel(String name) {
super(new BorderLayout());
setName(name);
textField = new JTextField(20);
browseButton = new BrowseButton();
browseButton.addActionListener(e -> showFileChooser());
add(textField, BorderLayout.CENTER);
add(browseButton, BorderLayout.EAST);
}
public void setValue(File value) {
String text = value == null ? "" : value.toString();
textField.setText(text);
}
private void showFileChooser() {
GhidraFileChooser chooser = new GhidraFileChooser(null);
chooser.setSelectedFile(getFile());
chooser.setTitle("Choose " + getName());
chooser.setFileSelectionMode(chooserMode);
if (startingDir != null) {
chooser.setCurrentDirectory(startingDir);
}
File selectedFile = chooser.getSelectedFile();
if (selectedFile != null) {
textField.setText(selectedFile.toString());
}
chooser.dispose();
}
public File getFile() {
String text = textField.getText().trim();
if (text.isBlank()) {
return null;
}
return new File(text);
}
}
}

View file

@ -0,0 +1,556 @@
/* ###
* 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 docking.widgets.values;
import java.io.File;
import java.util.*;
import docking.widgets.filechooser.GhidraFileChooserMode;
import ghidra.util.StatusListener;
/**
* Class for defining, storing, and retrieving groups of values of various types. The intended use
* is to create a ValuesMap, define some named values, and then invoke the ValuesMapDialog to allow
* the user to fill in values for the defined values. It also has a rich set of convenience methods
* for adding predefined value types to the map. Users can also directly add custom value types by
* using the {@link #addValue(AbstractValue)} method.
*/
public class GValuesMap {
protected Map<String, AbstractValue<?>> valuesMap = new LinkedHashMap<>();
private ValuesMapValidator validator;
/**
* Returns a collection of the AbstractValues defined in this ValuesMap.
* @return a collection of the AbstractValues defined in this ValuesMap.
*/
public Collection<AbstractValue<?>> getValues() {
return valuesMap.values();
}
/**
* Adds an AbstractValue to this ValuesMap. This is a way to add a custom AbstractValue that
* doesn't have a convenience method for a predefine value type.
* @param value the AbstractValue to add to this ValuesMap
* @return returns the added value
*/
public AbstractValue<?> addValue(AbstractValue<?> value) {
String name = value.getName();
checkDup(name);
valuesMap.put(name, value);
return value;
}
/**
* Sets a {@link ValuesMapValidator}. If set, this will be called when the user presses the
* "Ok" button on the {@link ValuesMapDialog}. If the validator passes (returns true), then
* the dialog will close and return the user values. Otherwise, the dialog will display the
* error message (via the {@link StatusListener} in the
* {@link ValuesMapValidator#validate(GValuesMap, StatusListener)} call) and remain open.
* @param validator the validator to be called before returning from the dialog
*/
public void setValidator(ValuesMapValidator validator) {
this.validator = validator;
}
/**
* The call to validate the data using the {@link ValuesMapValidator} set in the
* {@link #setValidator(ValuesMapValidator)} method. If no validator has been set,
* this method will return true.
* @param listener The {@link StatusListener} for reporting an error message.
* @return true if the validator passes or no validator has been set.
*/
public boolean isValid(StatusListener listener) {
if (validator != null) {
return validator.validate(this, listener);
}
return true;
}
/**
* Updates each value in this ValuesMap from its corresponding JComponent.
* @throws ValuesMapParseException if any value encountered an error trying to update its
* value from the editor component.
*/
public void updateFromComponents() throws ValuesMapParseException {
for (AbstractValue<?> inputValue : valuesMap.values()) {
inputValue.updateValueFromComponent();
}
}
/**
* Returns the AbstractValue for the given value name.
* @param name the name for which to get the AbstractValue
* @return the AbstractValue for the given value name.
*/
public AbstractValue<?> getAbstractValue(String name) {
return valuesMap.get(name);
}
/**
* Returns true if there is a defined value for the given name.
* @param name the name of the value to check for
* @return true if there is a defined value for the given name.
*/
public boolean isDefined(String name) {
return valuesMap.containsKey(name);
}
/**
* Returns true if the value defined for the given name has a non-null value.
* @param name the name of the value
* @return true if the value defined for the given name has a non-null value.
*/
public boolean hasValue(String name) {
AbstractValue<?> abstractValue = valuesMap.get(name);
if (abstractValue == null) {
throw new IllegalArgumentException("No value defined for " + name);
}
return abstractValue.hasValue();
}
/**
* Copies the values (not the AbstractValues objects, but the T values of each AbstractValue)
* from the given map into this map. The given map must have exactly the same name and
* AbstractValue types as this map.
* @param otherMap The GValuesMap to copy values from
* @throws IllegalArgumentException if the given map does not have exactly the same set of
* names and types as this this map
*/
@SuppressWarnings("unchecked")
public void copyValues(GValuesMap otherMap) {
for (AbstractValue<?> v : valuesMap.values()) {
AbstractValue<?> otherValue = otherMap.getAbstractValue(v.getName());
if (otherValue == null || otherValue.getClass() != v.getClass()) {
throw new IllegalArgumentException(
"Can't copy values from incompatable " + getClass().getSimpleName() + "s!");
}
v.copyValue(v.getClass().cast(otherValue));
}
}
//==================================================================================================
// Define Value Methods
//==================================================================================================
/**
* Defines a value of type Boolean.
* @param name the name for this value
* @param defaultValue the default value for this boolean value.
* @return the new BooleanValue that was defined.
*/
public BooleanValue defineBoolean(String name, boolean defaultValue) {
checkDup(name);
BooleanValue value = new BooleanValue(name, defaultValue);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type String, but with a restricted set of valid string values.
* @param name the name for this value.
* @param defaultValue an optional (can be null) initial value
* @param choices varargs list of valid string choices
* @return the new ChoiceValue that was defined
*/
public ChoiceValue defineChoice(String name, String defaultValue, String... choices) {
checkDup(name);
ChoiceValue value = new ChoiceValue(name, defaultValue, choices);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type File, but is restricted to directories.
* @param name the name for this value
* @param defaultValue an optional initial value
* @return the new FileValue that was defined
*/
public FileValue defineDirectory(String name, File defaultValue) {
checkDup(name);
FileValue value =
new FileValue(name, defaultValue, null, GhidraFileChooserMode.DIRECTORIES_ONLY);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Double with no initial default value.
* @param name the name for this value
* @return the new DoubleValue that was defined
*/
public DoubleValue defineDouble(String name) {
checkDup(name);
DoubleValue value = new DoubleValue(name, null);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Double with an initial value
* @param name the name for this value
* @param defaultValue the initial value
* @return the new DoubleValue that was defined
*/
public DoubleValue defineDouble(String name, double defaultValue) {
checkDup(name);
DoubleValue value = new DoubleValue(name, defaultValue);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type File
* @param name the name for this value
* @param defaultValue an optional initial value
* @return the new FileValue that was defined
*/
public FileValue defineFile(String name, File defaultValue) {
return defineFile(name, defaultValue, null);
}
/**
* Defines a value of type File
* @param name the name for this value
* @param defaultValue an optional initial value
* @param startingDir specifies the starting directory when the FileChooser is invoked
* @return the new FileValue that was defined
*/
public FileValue defineFile(String name, File defaultValue, File startingDir) {
checkDup(name);
FileValue value =
new FileValue(name, defaultValue, startingDir, GhidraFileChooserMode.FILES_ONLY);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Integer that displays as a hex value.
* @param name the name for this value
* @return the new IntValue that was defined
*/
public IntValue defineHexInt(String name) {
checkDup(name);
IntValue value = new IntValue(name, null, true);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Integer with an initial value and displays as a hex value.
* @param name the name for this value
* @param defaultValue the initial value
* @return the new IntValue that was defined
*/
public IntValue defineHexInt(String name, int defaultValue) {
checkDup(name);
IntValue value = new IntValue(name, defaultValue, true);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Long that displays as a hex value.
* @param name the name for this value
* @return the new LongValue that was defined
*/
public LongValue defineHexLong(String name) {
checkDup(name);
LongValue value = new LongValue(name, null, true);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Long with an initial value and displays as a hex value.
* @param name the name for this value
* @param defaultValue the initial value
* @return the new LongValue that was defined
*/
public LongValue defineHexLong(String name, long defaultValue) {
checkDup(name);
LongValue value = new LongValue(name, defaultValue, true);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Integer with no initial value.
* @param name the name for this value
* @return the new IntValue that was defined
*/
public IntValue defineInt(String name) {
checkDup(name);
IntValue value = new IntValue(name, null, false);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Integer with an initial value.
* @param name the name for this value
* @param defaultValue the initial value
* @return the new IntValue that was defined
*/
public IntValue defineInt(String name, int defaultValue) {
checkDup(name);
IntValue value = new IntValue(name, defaultValue, false);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Long with an initial value.
* @param name the name for this value
* @return the new LongValue that was defined
*/
public LongValue defineLong(String name) {
checkDup(name);
LongValue value = new LongValue(name, null, false);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type Long with an initial value.
* @param name the name for this value
* @param defaultValue the initial value
* @return the new LongValue that was defined
*/
public LongValue defineLong(String name, long defaultValue) {
checkDup(name);
LongValue value = new LongValue(name, defaultValue, false);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type String.
* @param name the name for this value
* @return the new StringValue that was defined
*/
public StringValue defineString(String name) {
checkDup(name);
StringValue value = new StringValue(name, null);
valuesMap.put(name, value);
return value;
}
/**
* Defines a value of type String with an optional initial value
* @param name the name for this value
* @param defaultValue the initial value (can be null)
* @return the new StringValue that was defined
*/
public StringValue defineString(String name, String defaultValue) {
checkDup(name);
StringValue value = new StringValue(name, defaultValue);
valuesMap.put(name, value);
return value;
}
//==================================================================================================
// Get Value Methods
//==================================================================================================
/**
* Gets the boolean value for the given name.
* @param name the name of a previously defined boolean value
* @return the boolean value
* @throws IllegalArgumentException if the name hasn't been defined as a boolean type
*/
public boolean getBoolean(String name) {
BooleanValue booleanValue = getValue(name, BooleanValue.class, "Boolean");
Boolean value = booleanValue.getValue();
return value == null ? false : value;
}
/**
* Gets the Choice (String) value for the given name. The value will be either null or one of
* the strings that were defined as valid choices.
* @param name the name of a previously defined Choice value
* @return the Choice value
* @throws IllegalArgumentException if the name hasn't been defined as a Choice type
*/
public String getChoice(String name) {
ChoiceValue choiceValue = getValue(name, ChoiceValue.class, "Choice");
return choiceValue.getValue();
}
/**
* Gets the double value for the given name.
* @param name the name of a previously defined double value
* @return the double value
* @throws IllegalArgumentException if the name hasn't been defined as a double type
*/
public double getDouble(String name) {
DoubleValue doubleValue = getValue(name, DoubleValue.class, "Double");
Double value = doubleValue.getValue();
return value == null ? 0.0 : value;
}
/**
* Gets the {@link File} value for the given name.
* @param name the name of a previously defined File value
* @return the File value
* @throws IllegalArgumentException if the name hasn't been defined as a File type
*/
public File getFile(String name) {
FileValue fileValue = getValue(name, FileValue.class, "File");
return fileValue.getValue();
}
/**
* Gets the int value for the given name.
* @param name the name of a previously defined int value
* @return the int value
* @throws IllegalArgumentException if the name hasn't been defined as a int type
*/
public int getInt(String name) {
IntValue intValue = getValue(name, IntValue.class, "Int");
Integer value = intValue.getValue();
return value == null ? 0 : value;
}
/**
* Gets the long value for the given name.
* @param name the name of a previously defined long value
* @return the long value
* @throws IllegalArgumentException if the name hasn't been defined as a long type
*/
public long getLong(String name) {
LongValue longValue = getValue(name, LongValue.class, "Int");
Long value = longValue.getValue();
return value == null ? 0 : value;
}
/**
* Gets the String value for the given name.
* @param name the name of a previously defined String value
* @return the String value
* @throws IllegalArgumentException if the name hasn't been defined as a String type
*/
public String getString(String name) {
StringValue stringValue = getValue(name, StringValue.class, "String");
return stringValue.getValue();
}
//==================================================================================================
// Set Value Methods
//==================================================================================================
/**
* Sets the boolean value for the given name.
* @param name the name of the boolean value that was previously defined
* @param value the boolean to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a boolean type
*/
public void setBoolean(String name, boolean value) {
BooleanValue booleanValue = getValue(name, BooleanValue.class, "Boolean");
booleanValue.setValue(value);
}
/**
* Sets the Choice (String) value for the given name.
* @param name the name of the Choice value that was previously defined
* @param choice the string to set as the value. This String must be one of the defined choices
* @throws IllegalArgumentException if the name hasn't been defined as a choice type
*/
public void setChoice(String name, String choice) {
ChoiceValue choiceValue = getValue(name, ChoiceValue.class, "Choice");
choiceValue.setValue(choice);
}
/**
* Sets the double value for the given name.
* @param name the name of the double value that was previously defined
* @param value the double to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a double type
*/
public void setDouble(String name, double value) {
DoubleValue doubleValue = getValue(name, DoubleValue.class, "Double");
doubleValue.setValue(value);
}
/**
* Sets the {@link File} value for the given name.
* @param name the name of the File value that was previously defined
* @param value the File to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a File type
*/
public void setFile(String name, File value) {
FileValue fileValue = getValue(name, FileValue.class, "File");
fileValue.setValue(value);
}
/**
* Sets the int value for the given name.
* @param name the name of the int value that was previously defined
* @param value the int to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a int type
*/
public void setInt(String name, int value) {
IntValue intValue = getValue(name, IntValue.class, "Int");
intValue.setValue(value);
}
/**
* Sets the long value for the given name.
* @param name the name of the long value that was previously defined
* @param value the long to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a long type
*/
public void setLong(String name, long value) {
LongValue intValue = getValue(name, LongValue.class, "Long");
intValue.setValue(value);
}
/**
* Sets the String value for the given name.
* @param name the name of the String value that was previously defined
* @param value the String to set as the value
* @throws IllegalArgumentException if the name hasn't been defined as a String type
*/
public void setString(String name, String value) {
StringValue stringValue = getValue(name, StringValue.class, "String");
stringValue.setValue(value);
}
//==================================================================================================
// Protected Methods
//==================================================================================================
@SuppressWarnings("unchecked")
protected <T> T getValue(String name, Class<T> c, String typeName) {
AbstractValue<?> value = valuesMap.get(name);
if (value == null) {
throw new IllegalArgumentException("No value defined for " + name);
}
if (value.getClass().isAssignableFrom(c)) {
return (T) value;
}
throw new IllegalArgumentException(
"Wrong type! No " + typeName + " value defined for: " + name);
}
protected void checkDup(String name) {
if (valuesMap.containsKey(name)) {
throw new IllegalArgumentException("value already exits named " + name);
}
}
}

View file

@ -0,0 +1,114 @@
/* ###
* 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 docking.widgets.values;
import javax.swing.JComponent;
import docking.widgets.textfield.IntegerTextField;
/**
* Value class for {@link Integer} Value with an option for display the value as decimal or hex. The
* editor component uses an {@link IntegerTextField} for display and editing the value. This
* value supports the concept of no value which is represented by the text field being empty. If
* the text field is not empty, then the field only allows valid numeric values.
* <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 IntValue extends AbstractValue<Integer> {
private boolean displayAsHex;
private IntegerTextField field;
/**
* Constructs an IntValue that displays it value in decimal
* @param name the name of the value
*/
public IntValue(String name) {
this(name, null, false);
}
/**
* Constructs an IntValue with a default value that displays it value in decimal
* @param name the name of the value
* @param defaultValue the default value
*/
public IntValue(String name, int defaultValue) {
this(name, defaultValue, false);
}
/**
* Constructs an IntValue with a default value.
* @param name the name of the value
* @param defaultValue the default value
* @param displayAsHex if true, the value will be displayed as hex, otherwise it will display
* as decimal.
*/
public IntValue(String name, Integer defaultValue, boolean displayAsHex) {
super(name, defaultValue);
this.displayAsHex = displayAsHex;
}
@Override
public JComponent getComponent() {
if (field == null) {
field = new IntegerTextField(20);
field.setAllowsHexPrefix(false);
field.setShowNumberMode(false);
if (displayAsHex) {
field.setHexMode();
field.setShowNumberMode(true);
}
}
return field.getComponent();
}
@Override
protected void updateValueFromComponent() {
String text = field.getText();
// special case where user didn't enter a value on a string field that was defined without
// a value
if (getValue() == null && text.equals("")) {
return;
}
setValue(field.getIntValue());
}
@Override
protected void updateComponentFromValue() {
Integer value = getValue();
if (value == null) {
field.setText("");
return;
}
field.setValue(value);
}
@Override
public Integer fromString(String valueString) {
return displayAsHex ? Integer.parseInt(valueString, 16) : Integer.parseInt(valueString, 10);
}
@Override
public String toString(Integer v) {
return displayAsHex ? Integer.toHexString(v) : v.toString();
}
}

View file

@ -0,0 +1,105 @@
/* ###
* 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 docking.widgets.values;
import javax.swing.JComponent;
import docking.widgets.textfield.IntegerTextField;
/**
* Value class for Long Values with an option for display the value as decimal or hex. The
* editor component uses an {@link IntegerTextField} for display and editing the value. This
* value supports the concept of no value which is represented by the text field being empty. If
* the text field is not empty, then the field only allows valid numeric values.
* <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 LongValue extends AbstractValue<Long> {
private boolean displayAsHex;
private IntegerTextField field;
public LongValue(String name) {
this(name, null, false);
}
public LongValue(String name, Long defaultValue) {
this(name, defaultValue, false);
}
public LongValue(String name, boolean displayAsHex) {
this(name, null, displayAsHex);
}
public LongValue(String name, Long defaultValue, boolean displayAsHex) {
super(name, defaultValue);
this.displayAsHex = displayAsHex;
}
@Override
public JComponent getComponent() {
if (field == null) {
field = new IntegerTextField(20);
field.setAllowsHexPrefix(false);
field.setShowNumberMode(false);
if (displayAsHex) {
field.setHexMode();
field.setShowNumberMode(true);
}
}
return field.getComponent();
}
@Override
protected void updateValueFromComponent() {
String text = field.getText();
// special case where user didn't enter a value on a string field that was defined without
// a value
if (getValue() == null && text.equals("")) {
return;
}
setValue(field.getLongValue());
}
@Override
protected void updateComponentFromValue() {
Long value = getValue();
if (value == null) {
field.setText("");
return;
}
field.setValue(value);
}
@Override
public Long fromString(String valueString) {
return displayAsHex ? Long.parseLong(valueString, 16) : Long.parseLong(valueString, 10);
}
@Override
public String getAsText() {
Long v = getValue();
if (v == null) {
return null;
}
return displayAsHex ? Long.toHexString(v) : Long.toString(v);
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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 docking.widgets.values;
import javax.swing.JComponent;
import javax.swing.JTextField;
/**
* Value class for {@link String} values.
* <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 StringValue extends AbstractValue<String> {
private JTextField textField;
public StringValue(String name) {
this(name, null);
}
public StringValue(String name, String defaultValue) {
super(name, defaultValue);
}
@Override
public JComponent getComponent() {
if (textField == null) {
textField = new JTextField(20);
}
return textField;
}
@Override
protected void updateValueFromComponent() {
String text = textField.getText();
// special case where user didn't enter a value on a string field that was defined without
// a value
if (getValue() == null && text.equals("")) {
return;
}
setValue(text);
}
@Override
protected void updateComponentFromValue() {
textField.setText(getValue());
}
@Override
public String fromString(String valueString) {
return valueString;
}
}

View file

@ -0,0 +1,168 @@
/* ###
* 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 docking.widgets.values;
import java.awt.BorderLayout;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.label.GHtmlLabel;
import ghidra.util.HTMLUtilities;
import ghidra.util.MessageType;
import ghidra.util.layout.PairLayout;
/**
* Dialog for displaying and editing values defined in a {@link GValuesMap}. The dialog consists
* of an option message, followed by a list of name / value pairs. The name / value pairs will
* be display in the order they were defined in the ValuesMap.
*/
public class ValuesMapDialog extends DialogComponentProvider {
private static final int MAX_MESSAGE_LINE_WIDTH = 60;
private JPanel valuesPanel;
private GValuesMap valuesMap;
private boolean cancelled = false;
private String message;
/**
* Creates the dialog with the given title and optional message. The message will be display
* at the top of the dialog before the list of name / value pairs. This form of the dialog
* requires that the {@link #addValue(AbstractValue)} method be called to populate the
* ValuesMap.
* @param title the title for the dialog
* @param message the optional message to display before the list of name value pairs
*/
public ValuesMapDialog(String title, String message) {
this(title, message, new GValuesMap());
}
/**
* Creates the dialog with the given title and optional message. The message will be display
* at the top of the dialog before the list of name / value pairs. The values are provided
* at construction time.
* @param title the title for the dialog
* @param message the optional message to display before the list of name value pairs
* @param valuesMap the ValuesMap whose values are to be displayed.
*/
public ValuesMapDialog(String title, String message, GValuesMap valuesMap) {
super(title);
this.message = message;
this.valuesMap = valuesMap;
valuesPanel = buildValuesPanel();
addWorkPanel(buildWorkPanel());
for (AbstractValue<?> value : valuesMap.getValues()) {
buildComponentsForValue(value);
}
setRememberSize(false);
addOKButton();
addCancelButton();
}
/**
* Adds a new value to the ValuesMap being edited by this dialog.
* @param value the new AbstractValue to be added
* @return the value that was added
*/
public AbstractValue<?> addValue(AbstractValue<?> value) {
valuesMap.addValue(value);
buildComponentsForValue(value);
return value;
}
/**
* Sets the {@link ValuesMapValidator} on the ValuesMap being edited. This is usually set on the
* ValuesMap before the dialog is constructed. This method is for uses where it wasn't
* constructed with a ValueMap, but values were added directly to the dialog after dialog
* construction.
* @param validator the ValuesMapValidator
*/
public void setValidator(ValuesMapValidator validator) {
valuesMap.setValidator(validator);
}
/**
* Returns the ValuesMap being edited.
* @return the ValuesMap being edited.
*/
public GValuesMap getValues() {
if (cancelled) {
return null;
}
return valuesMap;
}
/**
* Returns true if the dialog was cancelled.
* @return true if the dialog was cancelled.
*/
public boolean isCancelled() {
return cancelled;
}
@Override
protected void okCallback() {
try {
valuesMap.updateFromComponents();
}
catch (ValuesMapParseException e) {
setStatusText(e.getMessage(), MessageType.ERROR);
return;
}
if (valuesMap.isValid(this)) {
close();
}
}
@Override
protected void cancelCallback() {
cancelled = true;
super.cancelCallback();
}
private JPanel buildValuesPanel() {
JPanel panel = new JPanel(new PairLayout(4, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
return panel;
}
private JComponent buildWorkPanel() {
JPanel panel = new JPanel(new BorderLayout());
if (message != null) {
String literalHTML = HTMLUtilities.toLiteralHTML(message, MAX_MESSAGE_LINE_WIDTH);
GHtmlLabel label = new GHtmlLabel(literalHTML);
label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(label, BorderLayout.NORTH);
}
JScrollPane scroll = new JScrollPane(valuesPanel);
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
panel.add(scroll, BorderLayout.CENTER);
return panel;
}
private void buildComponentsForValue(AbstractValue<?> value) {
valuesPanel.add(new JLabel(value.getName() + ":", SwingConstants.RIGHT));
valuesPanel.add(value.getComponent());
value.updateComponentFromValue();
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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 docking.widgets.values;
/**
* Exception thrown when processing/parsing ValuesMap values. Mostly exists so that the exception
* message is uniform throught the types.
*/
public class ValuesMapParseException extends Exception {
/**
* Constructor
* @param valueName the name of the value that was being processed
* @param type the type name of the value that was being processed
* @param message the detail message of what went wrong
*/
public ValuesMapParseException(String valueName, String type, String message) {
super("Error processing " + type + " value \"" + valueName + "\"! " + message);
}
}

View file

@ -0,0 +1,36 @@
/* ###
* 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 docking.widgets.values;
import ghidra.util.StatusListener;
/**
* Interface for validating values in a {@link GValuesMap}
*/
public interface ValuesMapValidator {
/**
* Validates one or more values in the given ValuesMap. This is used by the ValuesMapDialog
* to validate values when the user presses the "Ok" button. If it returns true, the dialog
* will close. Otherwise, the dialog will remain visible, displaying the error message that
* was reported to the given StatusListener.
* @param values the ValuesMap whose values are to be validated
* @param statusListener a {@link StatusListener} to report validation errors back to
* the dialog
* @return true if the values pass the validation check.
*/
public boolean validate(GValuesMap values, StatusListener statusListener);
}

View file

@ -0,0 +1,57 @@
/* ###
* 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 docking.widgets.values;
import javax.swing.JButton;
import javax.swing.JTextField;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.test.AbstractDockingTest;
public abstract class AbstractValueTest extends AbstractDockingTest {
protected ValuesMapDialog dialog;
protected GValuesMap values = new GValuesMap();
protected void showDialogOnSwingWithoutBlocking() {
runSwing(() -> {
dialog = new ValuesMapDialog("Test", null, values);
DockingWindowManager.showDialog(dialog);
}, false);
waitForDialogComponent(DialogComponentProvider.class);
}
protected void setTextOnComponent(AbstractValue<?> nameValue, String text) {
runSwing(() -> {
JTextField field = (JTextField) nameValue.getComponent();
field.setText(text);
});
}
protected void pressOk() {
JButton okButton = (JButton) getInstanceField("okButton", dialog);
runSwing(() -> okButton.doClick());
}
protected void pressCancel() {
JButton okButton = (JButton) getInstanceField("cancelButton", dialog);
runSwing(() -> okButton.doClick());
}
}

View file

@ -0,0 +1,90 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import javax.swing.JCheckBox;
import org.junit.Test;
import docking.widgets.values.AbstractValue;
import docking.widgets.values.BooleanValue;
public class BooleanValueTest extends AbstractValueTest {
private static final String NAME = "YesNo";
@Test
public void testBooleanValue() {
values.defineBoolean(NAME, false);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(false, values.getBoolean(NAME));
values.setBoolean(NAME, true);
assertTrue(values.hasValue(NAME));
assertEquals(true, values.getBoolean(NAME));
}
@Test
public void testGetAsText() {
BooleanValue value1 = new BooleanValue(NAME, true);
BooleanValue value2 = new BooleanValue(NAME, false);
assertEquals("true", value1.getAsText());
assertEquals("false", value2.getAsText());
}
@Test
public void testSetAsText() {
BooleanValue result = new BooleanValue(NAME, false);
assertTrue(result.setAsText("true"));
assertFalse(result.setAsText("false"));
assertTrue(result.setAsText("TRUE"));
assertFalse(result.setAsText("asdas"));
}
@Test
public void testValueWithNoDialogInput() {
values.defineBoolean(NAME, false);
showDialogOnSwingWithoutBlocking();
pressOk();
assertEquals(false, values.getBoolean(NAME));
}
@Test
public void testValueWithDialogInput() {
values.defineBoolean(NAME, false);
showDialogOnSwingWithoutBlocking();
setBoolean(values.getAbstractValue(NAME), true);
pressOk();
assertEquals(true, values.getBoolean(NAME));
}
private void setBoolean(AbstractValue<?> boolValue, boolean b) {
runSwing(() -> {
JCheckBox checkBox = (JCheckBox) boolValue.getComponent();
checkBox.setSelected(b);
});
}
}

View file

@ -0,0 +1,151 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.combobox.GComboBox;
import docking.widgets.values.AbstractValue;
import docking.widgets.values.ChoiceValue;
public class ChoiceValueTest extends AbstractValueTest {
private static final String NAME = "Choice";
@Test
public void testChoiceValueNoDefault() {
values.defineChoice(NAME, null, "A", "B", "C");
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setChoice(NAME, "B");
assertTrue(values.hasValue(NAME));
assertEquals("B", values.getChoice(NAME));
}
@Test
public void testChoiceValueWithDefault() {
values.defineChoice(NAME, "A", "A", "B", "C");
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals("A", values.getChoice(NAME));
values.setChoice(NAME, "C");
assertTrue(values.hasValue(NAME));
assertEquals("C", values.getChoice(NAME));
values.setChoice(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testGetAsText() {
ChoiceValue v1 = new ChoiceValue(NAME, "A", "A", "B");
ChoiceValue v2 = new ChoiceValue(NAME, null, "A", "B");
assertEquals("A", v1.getAsText());
assertNull(v2.getAsText());
}
@Test
public void testSetAsText() {
ChoiceValue result = new ChoiceValue(NAME, null, "A", "B");
assertEquals("A", result.setAsText("A"));
try {
result.setAsText("Z");
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testChoiceWithInValidDefault() {
try {
values.defineChoice(NAME, "Z", "A", "B", "C");
fail("Was able to set bad default value in choice");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineChoice(NAME, null, "A", "B", "C");
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getChoice(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineChoice(NAME, null, "A", "B", "C");
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setCombo(values.getAbstractValue(NAME), "C");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals("C", values.getChoice(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineChoice(NAME, "B", "A", "B", "C");
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals("B", values.getChoice(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineChoice(NAME, "C", "A", "B", "C");
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setCombo(values.getAbstractValue(NAME), "A");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals("A", values.getChoice(NAME));
}
private void setCombo(AbstractValue<?> choiceValue, String choice) {
runSwing(() -> {
GComboBox<?> combo = (GComboBox<?>) choiceValue.getComponent();
combo.setSelectedItem(choice);
});
}
}

View file

@ -0,0 +1,121 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.values.DoubleValue;
public class DoubleValueTest extends AbstractValueTest {
private static final String NAME = "Fraction";
private static double DELTA = 0.0001;
@Test
public void testDoubleValueNoDefault() {
values.defineDouble(NAME);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
assertEquals(0, values.getDouble(NAME), DELTA); // the getPrimitive returns 0 when value is null
values.setDouble(NAME, 6);
assertTrue(values.hasValue(NAME));
assertEquals(6, values.getDouble(NAME), DELTA);
}
@Test
public void testDoubleValueWithDefault() {
values.defineDouble(NAME, 3.2);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(3.2, values.getDouble(NAME), DELTA);
values.setDouble(NAME, 6.5);
assertTrue(values.hasValue(NAME));
assertEquals(6.5, values.getDouble(NAME), DELTA);
}
@Test
public void testGetAsText() {
DoubleValue v1 = new DoubleValue(NAME, 1.23);
DoubleValue v2 = new DoubleValue(NAME);
assertEquals("1.23", v1.getAsText());
assertNull(v2.getAsText());
}
@Test
public void testSetAsText() {
DoubleValue v1 = new DoubleValue(NAME);
assertEquals((Double) 1.23, v1.setAsText("1.23"));
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineDouble(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertEquals(0, values.getDouble(NAME), DELTA);
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineDouble(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "1.23");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(1.23, values.getDouble(NAME), DELTA);
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineDouble(NAME, 1.2);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(1.2, values.getDouble(NAME), DELTA);
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineDouble(NAME, 1.2);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "4.3");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(4.3, values.getDouble(NAME), DELTA);
}
}

View file

@ -0,0 +1,204 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.values.AbstractValue;
import docking.widgets.values.FileValue;
import docking.widgets.values.FileValue.FileValuePanel;
public class FileValueTest extends AbstractValueTest {
private static final String NAME = "My File";
@Test
public void testFileValueNoDefault() {
values.defineFile(NAME, null);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setFile(NAME, new File("."));
assertTrue(values.hasValue(NAME));
assertEquals(new File("."), values.getFile(NAME));
}
@Test
public void testFileValueWithDefault() {
values.defineFile(NAME, new File("/"));
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(new File("/"), values.getFile(NAME));
values.setFile(NAME, new File("."));
assertTrue(values.hasValue(NAME));
assertEquals(new File("."), values.getFile(NAME));
values.setFile(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testGetAsText() {
FileValue value1 = new FileValue(NAME);
FileValue value2 = new FileValue(NAME, new File("/"));
assertNull(value1.getAsText());
assertEquals("/", value2.getAsText());
}
@Test
public void testSetAsText() {
FileValue v = new FileValue(NAME);
assertEquals(new File("/abc"), v.setAsText("/abc"));
try {
v.setAsText(null);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testDirectoryValueNoDefault() {
values.defineDirectory(NAME, null);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setFile(NAME, new File("."));
assertTrue(values.hasValue(NAME));
assertEquals(new File("."), values.getFile(NAME));
}
@Test
public void testDirectoryValueWithDefault() {
values.defineDirectory(NAME, new File("/"));
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(new File("/"), values.getFile(NAME));
values.setFile(NAME, new File("."));
assertTrue(values.hasValue(NAME));
assertEquals(new File("."), values.getFile(NAME));
values.setFile(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineFile(NAME, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getFile(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() throws IOException {
File foo = createTempFile("foo");
values.defineFile(NAME, null);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setFile(values.getAbstractValue(NAME), foo);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(foo, values.getFile(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineFile(NAME, new File("/"));
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(new File("/"), values.getFile(NAME));
}
@Test
public void testDefaultValueWithDialogInput() throws IOException {
File foo = createTempFile("foo");
File bar = createTempFile("bar");
values.defineFile(NAME, foo);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setFile(values.getAbstractValue(NAME), bar);
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(bar, values.getFile(NAME));
}
@Test
public void testDirectoryWithDialogInput() throws IOException {
File dir = createTempDirectory("foo");
values.defineDirectory(NAME, null);
showDialogOnSwingWithoutBlocking();
setFile(values.getAbstractValue(NAME), dir);
pressOk();
assertEquals(dir, values.getFile(NAME));
}
@Test
public void testStartingDir() throws IOException {
File file = createTempDirectory("foo");
File parent = file.getParentFile();
values.defineFile(NAME, null, parent);
showDialogOnSwingWithoutBlocking();
FileValuePanel fileWidget = (FileValuePanel) values.getAbstractValue(NAME).getComponent();
pressButtonByName(fileWidget, "BrowseButton", false);
GhidraFileChooser chooser = waitForDialogComponent(GhidraFileChooser.class);
File dir = runSwing(() -> chooser.getCurrentDirectory());
pressButtonByText(chooser, "Cancel");
pressOk();
assertEquals(parent, dir);
}
protected void setFile(AbstractValue<?> nameValue, File f) {
FileValuePanel fileWidget = (FileValuePanel) nameValue.getComponent();
pressButtonByName(fileWidget, "BrowseButton", false);
GhidraFileChooser chooser = waitForDialogComponent(GhidraFileChooser.class);
runSwing(() -> {
chooser.setSelectedFile(f);
});
pressButtonByText(chooser, "OK");
}
}

View file

@ -0,0 +1,137 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.values.IntValue;
public class IntValueTest extends AbstractValueTest {
private static final String NAME = "Count";
@Test
public void testIntValueNoDefault() {
values.defineInt(NAME);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
assertEquals(0, values.getInt(NAME)); // the getPrimitive returns 0 when value is null
values.setInt(NAME, 6);
assertTrue(values.hasValue(NAME));
assertEquals(6, values.getInt(NAME));
}
@Test
public void testIntValueWithDefault() {
values.defineInt(NAME, 32);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(32, values.getInt(NAME));
values.setInt(NAME, 6);
assertTrue(values.hasValue(NAME));
assertEquals(6, values.getInt(NAME));
}
@Test
public void testGetAsText() {
IntValue v1 = new IntValue(NAME, 12);
IntValue v2 = new IntValue(NAME);
IntValue v3 = new IntValue(NAME, 10, true /*displayAsHex*/);
assertEquals("12", v1.getAsText());
assertNull(v2.getAsText());
assertEquals("a", v3.getAsText());
}
@Test
public void testSetAsText() {
IntValue v1 = new IntValue(NAME);
IntValue v2 = new IntValue(NAME, null, true /*displayAsText*/);
assertEquals((Integer) 10, v1.setAsText("10"));
assertEquals((Integer) 16, v2.setAsText("10"));
assertEquals((Integer) 10, v2.setAsText("A"));
assertEquals((Integer) 10, v2.setAsText("a"));
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineInt(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertEquals(0, values.getInt(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineInt(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "123");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(123, values.getInt(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineInt(NAME, 12);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(12, values.getInt(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineInt(NAME, 12);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "43");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(43, values.getInt(NAME));
}
@Test
public void testHexMode() {
values.defineHexInt(NAME, 12);
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "A");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(10, values.getInt(NAME));
}
}

View file

@ -0,0 +1,137 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.values.LongValue;
public class LongValueTest extends AbstractValueTest {
private static final String NAME = "Count";
@Test
public void testlongValueNoDefault() {
values.defineLong(NAME);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
assertEquals(0, values.getLong(NAME)); // the getPrimitive returns 0 when value is null
values.setLong(NAME, 6);
assertTrue(values.hasValue(NAME));
assertEquals(6, values.getLong(NAME));
}
@Test
public void testlongValueWithDefault() {
values.defineLong(NAME, 32);
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
assertEquals(32, values.getLong(NAME));
values.setLong(NAME, 6);
assertTrue(values.hasValue(NAME));
assertEquals(6, values.getLong(NAME));
}
@Test
public void testGetAsText() {
LongValue v1 = new LongValue(NAME, 12L);
LongValue v2 = new LongValue(NAME);
LongValue v3 = new LongValue(NAME, 10L, true /*displayAsHex*/);
assertEquals("12", v1.getAsText());
assertNull(v2.getAsText());
assertEquals("a", v3.getAsText());
}
@Test
public void testSetAsText() {
LongValue v1 = new LongValue(NAME);
LongValue v2 = new LongValue(NAME, null, true /*displayAsText*/);
assertEquals((Long) 10L, v1.setAsText("10"));
assertEquals((Long) 16L, v2.setAsText("10"));
assertEquals((Long) 10L, v2.setAsText("A"));
assertEquals((Long) 10L, v2.setAsText("a"));
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineLong(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertEquals(0, values.getLong(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineLong(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "123");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(123, values.getLong(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineLong(NAME, 12);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(12, values.getLong(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineLong(NAME, 12);
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "43");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(43, values.getLong(NAME));
}
@Test
public void testHexMode() {
values.defineHexLong(NAME, 12);
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "A");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals(10, values.getLong(NAME));
}
}

View file

@ -0,0 +1,129 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import org.junit.Test;
import docking.widgets.values.StringValue;
public class StringValueTest extends AbstractValueTest {
private static final String NAME = "Name";
@Test
public void testStringValueNoDefault() {
values.defineString(NAME);
assertTrue(values.isDefined(NAME));
assertFalse(values.hasValue(NAME));
values.setString(NAME, "abc");
assertTrue(values.hasValue(NAME));
assertEquals("abc", values.getString(NAME));
}
@Test
public void testStringValueWithDefault() {
values.defineString(NAME, "ABC");
assertTrue(values.isDefined(NAME));
assertTrue(values.hasValue(NAME));
values.setString(NAME, "xyz");
assertTrue(values.hasValue(NAME));
assertEquals("xyz", values.getString(NAME));
values.setString(NAME, null);
assertFalse(values.hasValue(NAME));
}
@Test
public void testGetAsText() {
StringValue v1 = new StringValue(NAME, "A");
StringValue v2 = new StringValue(NAME);
assertEquals("A", v1.getAsText());
assertNull(v2.getAsText());
}
@Test
public void testSetAsText() {
StringValue result = new StringValue(NAME);
assertEquals("A", result.setAsText("A"));
try {
result.setAsText(null);
fail("Expected exception");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testNoDefaultValueWithNoDialogInput() {
values.defineString(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertFalse(values.hasValue(NAME));
assertNull(values.getString(NAME));
}
@Test
public void testNoDefaultValueWithDialogInput() {
values.defineString(NAME);
assertFalse(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "xyz");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals("xyz", values.getString(NAME));
}
@Test
public void testDefaultValueWithNoDialogInput() {
values.defineString(NAME, "abc");
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals("abc", values.getString(NAME));
}
@Test
public void testDefaultValueWithDialogInput() {
values.defineString(NAME, "abc");
assertTrue(values.hasValue(NAME));
showDialogOnSwingWithoutBlocking();
setTextOnComponent(values.getAbstractValue(NAME), "xyz");
pressOk();
assertTrue(values.hasValue(NAME));
assertEquals("xyz", values.getString(NAME));
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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 docking.widgets.values;
import static org.junit.Assert.*;
import org.junit.Test;
public class ValuesMapDialogTest extends AbstractValueTest {
@Test
public void testGetValueAsWrongType() {
AbstractValue<Long> ageValue = values.defineLong("Age");
showDialogOnSwingWithoutBlocking();
setTextOnComponent(ageValue, "42");
pressOk();
try {
assertEquals(42, values.getInt("Age"));
fail("Should not be able to retrieve a value with the wrong type!");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testCancelDoesntChangeValue() {
AbstractValue<String> nameValue = values.defineString("Name", "abc");
showDialogOnSwingWithoutBlocking();
setTextOnComponent(nameValue, "Joe");
pressCancel();
assertTrue(dialog.isCancelled());
assertEquals("abc", values.getString("Name"));
}
}