mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-1403 added suggested string setting support
This commit is contained in:
parent
3acd14c48a
commit
362bd6b5cb
11 changed files with 255 additions and 20 deletions
|
@ -248,6 +248,13 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
appliedSettings = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get suggested string setting values from the original settings container.
|
||||
* @param settingsDefinition string settings definition
|
||||
* @return suggested string value (may be empty array or null)
|
||||
*/
|
||||
abstract String[] getSuggestedValues(StringSettingsDefinition settingsDefinition);
|
||||
|
||||
/**
|
||||
* Apply changes to settings. This method must be ov
|
||||
* @throws CancelledException thrown if apply operation cancelled
|
||||
|
@ -404,9 +411,9 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
else if (definition instanceof StringSettingsDefinition) {
|
||||
StringSettingsDefinition def = (StringSettingsDefinition) definition;
|
||||
if (defaultSettings == null && !def.hasValue(settings)) {
|
||||
return new StringWrapper(null); // show blank value
|
||||
return new StringWrapper(def, null); // show blank value
|
||||
}
|
||||
return new StringWrapper(def.getValue(settings));
|
||||
return new StringWrapper(def, def.getValue(settings));
|
||||
}
|
||||
return "<Unsupported>";
|
||||
}
|
||||
|
@ -636,16 +643,26 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
|
||||
private class StringWrapper {
|
||||
|
||||
final StringSettingsDefinition settingsDefinition;
|
||||
final String value; // may be null
|
||||
|
||||
StringWrapper(String value) {
|
||||
StringWrapper(StringSettingsDefinition settingsDefinition, String value) {
|
||||
this.value = value;
|
||||
this.settingsDefinition = settingsDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
StringChoices getStringChoices() {
|
||||
String[] suggestedValues = getSuggestedValues(settingsDefinition);
|
||||
if (suggestedValues == null) {
|
||||
return null;
|
||||
}
|
||||
return suggestedValues.length == 0 ? null : new StringChoices(suggestedValues);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsEditor extends AbstractCellEditor implements TableCellEditor {
|
||||
|
@ -654,6 +671,7 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
final static int BOOLEAN = 1;
|
||||
final static int NUMBER = 2;
|
||||
final static int STRING = 3;
|
||||
final static int STRING_WITH_SUGGESTIONS = 4;
|
||||
|
||||
private int mode;
|
||||
private GComboBox<String> comboBox = new GComboBox<>();
|
||||
|
@ -681,6 +699,7 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
case NUMBER:
|
||||
return getNumber();
|
||||
case STRING:
|
||||
case STRING_WITH_SUGGESTIONS:
|
||||
return getString();
|
||||
}
|
||||
throw new AssertException();
|
||||
|
@ -709,6 +728,9 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
private String getString() {
|
||||
if (mode == STRING_WITH_SUGGESTIONS) {
|
||||
return comboBox.getEditor().getItem().toString();
|
||||
}
|
||||
String value = textField.getText().trim();
|
||||
return value.length() == 0 ? null : value;
|
||||
}
|
||||
|
@ -726,8 +748,14 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
return intTextField.getComponent();
|
||||
}
|
||||
if (value instanceof StringWrapper) {
|
||||
initTextField(((StringWrapper) value).value);
|
||||
return textField;
|
||||
StringWrapper strWrapper = (StringWrapper) value;
|
||||
StringChoices strWithChoices = strWrapper.getStringChoices();
|
||||
if (strWithChoices == null) {
|
||||
initTextField(strWrapper.value);
|
||||
return textField;
|
||||
}
|
||||
initEditableComboBox(strWithChoices, strWrapper.value);
|
||||
return comboBox;
|
||||
}
|
||||
throw new AssertException(
|
||||
"SettingsEditor: " + value.getClass().getName() + " not supported");
|
||||
|
@ -736,6 +764,7 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
private void initComboBox(StringChoices enuum) {
|
||||
mode = ENUM;
|
||||
comboBox.removeAllItems();
|
||||
comboBox.setEditable(false);
|
||||
String[] items = enuum.getValues();
|
||||
for (String item : items) {
|
||||
comboBox.addItem(item);
|
||||
|
@ -743,6 +772,17 @@ public abstract class AbstractSettingsDialog extends DialogComponentProvider {
|
|||
comboBox.setSelectedIndex(enuum.getSelectedValueIndex());
|
||||
}
|
||||
|
||||
private void initEditableComboBox(StringChoices strChoices, String value) {
|
||||
mode = STRING_WITH_SUGGESTIONS;
|
||||
comboBox.removeAllItems();
|
||||
comboBox.setEditable(true);
|
||||
String[] items = strChoices.getValues();
|
||||
for (String item : items) {
|
||||
comboBox.addItem(item);
|
||||
}
|
||||
comboBox.getEditor().setItem(value);
|
||||
}
|
||||
|
||||
private void initIntField(Number value) {
|
||||
mode = NUMBER;
|
||||
NumberSettingsDefinition def = (NumberSettingsDefinition) rowobject.definition;
|
||||
|
|
|
@ -33,6 +33,7 @@ public class DataSettingsDialog extends AbstractSettingsDialog {
|
|||
private ProgramSelection selection; // Only set for data selection mode
|
||||
private Data data; // null for selection use
|
||||
private Program program;
|
||||
private Settings sampleSelectionSettings; // used to obtain suggested string values for selection case
|
||||
|
||||
/**
|
||||
* Construct for data instance settings based upon selection
|
||||
|
@ -334,6 +335,27 @@ public class DataSettingsDialog extends AbstractSettingsDialog {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
|
||||
if (!settingsDefinition.supportsSuggestedValues()) {
|
||||
return null;
|
||||
}
|
||||
if (data != null) {
|
||||
return settingsDefinition.getSuggestedValues(data);
|
||||
}
|
||||
if (sampleSelectionSettings == null) {
|
||||
DataIterator definedData = program.getListing().getDefinedData(selection, true);
|
||||
while (definedData.hasNext()) {
|
||||
sampleSelectionSettings = definedData.next();
|
||||
break;
|
||||
}
|
||||
if (sampleSelectionSettings == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return settingsDefinition.getSuggestedValues(sampleSelectionSettings);
|
||||
}
|
||||
|
||||
protected void applySettings() throws CancelledException {
|
||||
int txId = program.startTransaction(getTitle());
|
||||
try {
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.data;
|
||||
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.docking.settings.SettingsDefinition;
|
||||
import ghidra.docking.settings.*;
|
||||
import ghidra.program.database.data.DataTypeManagerDB;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
@ -133,20 +132,29 @@ public class DataTypeSettingsDialog extends AbstractSettingsDialog {
|
|||
return dt;
|
||||
}
|
||||
|
||||
private Settings getOriginalSettings() {
|
||||
if (dtc != null) {
|
||||
return dtc.getDefaultSettings();
|
||||
}
|
||||
return dataType.getDefaultSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
|
||||
if (settingsDefinition.supportsSuggestedValues()) {
|
||||
return settingsDefinition.getSuggestedValues(getOriginalSettings());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void applySettings() {
|
||||
DataTypeManager dtm = dataType.getDataTypeManager();
|
||||
int txId = dtm.startTransaction(getTitle());
|
||||
try {
|
||||
Settings origDefSettings = null;
|
||||
if (dtc != null) {
|
||||
origDefSettings = dtc.getDefaultSettings();
|
||||
}
|
||||
else {
|
||||
origDefSettings = dataType.getDefaultSettings();
|
||||
}
|
||||
Settings originalSettings = getOriginalSettings();
|
||||
Settings modifiedSettings = getSettings();
|
||||
for (SettingsDefinition settingsDef : getSettingsDefinitions()) {
|
||||
settingsDef.copySetting(modifiedSettings, origDefSettings);
|
||||
settingsDef.copySetting(modifiedSettings, originalSettings);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
|
|
@ -22,6 +22,8 @@ package ghidra.docking.settings;
|
|||
*/
|
||||
public interface Settings {
|
||||
|
||||
static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
/**
|
||||
* Determine if a settings change corresponding to the specified
|
||||
* settingsDefinition is permitted.
|
||||
|
@ -30,6 +32,15 @@ public interface Settings {
|
|||
*/
|
||||
boolean isChangeAllowed(SettingsDefinition settingsDefinition);
|
||||
|
||||
/**
|
||||
* Get an array of suggested values for the specified string settings definition.
|
||||
* @param settingsDefinition string settings definition
|
||||
* @return suggested values array (may be empty)
|
||||
*/
|
||||
default String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Long value associated with the given name
|
||||
* @param name the key used to retrieve a value
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.docking.settings;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public interface StringSettingsDefinition extends SettingsDefinition {
|
||||
|
||||
|
@ -43,4 +44,36 @@ public interface StringSettingsDefinition extends SettingsDefinition {
|
|||
public default boolean hasSameValue(Settings settings1, Settings settings2) {
|
||||
return Objects.equals(getValue(settings1), getValue(settings2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get suggested setting values
|
||||
* @param settings settings object
|
||||
* @return suggested settings or null if none or unsupported;
|
||||
*/
|
||||
public default String[] getSuggestedValues(Settings settings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this settings definition supports suggested values.
|
||||
* See {@link #getSuggestedValues(Settings)}.
|
||||
* @return true if suggested values are supported, else false.
|
||||
*/
|
||||
public default boolean supportsSuggestedValues() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add preferred setting values to the specified set as obtained from the specified
|
||||
* settingsOwner.
|
||||
* @param settingsOwner settings owner from which a definition may query preferred values.
|
||||
* Supported values are specific to this settings definition. An unsupported settingsOwner
|
||||
* will return false.
|
||||
* @param set value set to which values should be added
|
||||
* @return true if settingsOwner is supported and set updated, else false.
|
||||
*/
|
||||
public default boolean addPreferredValues(Object settingsOwner, Set<String> set) {
|
||||
// TODO: improve specification of settingsOwner
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ import db.*;
|
|||
import db.util.ErrorHandler;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.docking.settings.SettingsDefinition;
|
||||
import ghidra.docking.settings.*;
|
||||
import ghidra.framework.store.db.PackedDBHandle;
|
||||
import ghidra.framework.store.db.PackedDatabase;
|
||||
import ghidra.graph.*;
|
||||
|
@ -121,6 +120,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
|
|||
private SettingsCache<Long> settingsCache = new SettingsCache<>(200);
|
||||
private List<DataType> sortedDataTypes;
|
||||
private Map<Long, Set<String>> enumValueMap;
|
||||
private Map<String, Set<String>> suggestedSettingsValuesMap = new HashMap<>();
|
||||
|
||||
private List<InvalidatedListener> invalidatedListeners = new ArrayList<>();
|
||||
protected DataTypeManagerChangeListenerHandler defaultListener =
|
||||
|
@ -678,6 +678,13 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
|
|||
if (rec != null) {
|
||||
SettingDB setting = new SettingDB(rec, settingsAdapter.getSettingName(rec));
|
||||
settingsCache.put(dataTypeId, name, setting);
|
||||
if (strValue != null) {
|
||||
Set<String> suggestions = suggestedSettingsValuesMap.get(name);
|
||||
if (suggestions != null) {
|
||||
// only cache suggestion if suggestions previously requested
|
||||
suggestions.add(strValue);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -691,6 +698,39 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
private Set<String> generateSuggestions(StringSettingsDefinition settingsDefinition) {
|
||||
Set<String> set = new TreeSet<>();
|
||||
try {
|
||||
settingsAdapter.addAllValues(settingsDefinition.getStorageKey(), set);
|
||||
settingsDefinition.addPreferredValues(this, set);
|
||||
}
|
||||
catch (IOException e) {
|
||||
errHandler.dbError(e);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get suggested setting values for a specified settingsDefinition
|
||||
* @param settingsDefinition string settings definition
|
||||
* @return suggested values or empty array if none
|
||||
*/
|
||||
String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
|
||||
lock.acquire();
|
||||
try {
|
||||
Set<String> set = suggestedSettingsValuesMap
|
||||
.computeIfAbsent(settingsDefinition.getStorageKey(),
|
||||
n -> generateSuggestions(settingsDefinition));
|
||||
if (set.isEmpty()) {
|
||||
return Settings.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
return set.toArray(new String[set.size()]);
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if transaction is active. With proper lock established
|
||||
* this method may be useful for determining if a lazy record update
|
||||
|
@ -3200,7 +3240,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
|
|||
fireInvalidated();
|
||||
updateFavorites();
|
||||
idsToDataTypeMap.clear();
|
||||
|
||||
suggestedSettingsValuesMap.clear();
|
||||
}
|
||||
finally {
|
||||
lock.release();
|
||||
|
|
|
@ -89,6 +89,11 @@ class DataTypeSettingsDB implements Settings {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSuggestedValues(StringSettingsDefinition settingsDefinition) {
|
||||
return dataMgr.getSuggestedValues(settingsDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set predicate for settings modification
|
||||
* @param allowedSettingPredicate callback for checking an allowed setting modification
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.program.database.data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import db.*;
|
||||
import ghidra.program.database.map.AddressMap;
|
||||
|
@ -289,6 +290,14 @@ abstract class SettingsDBAdapter {
|
|||
*/
|
||||
abstract String[] getSettingsNames(long associationId) throws IOException;
|
||||
|
||||
/**
|
||||
* Add all values stored for the specified setting name to the specified set.
|
||||
* @param name setting name
|
||||
* @param set value set
|
||||
* @throws IOException if there was a problem accessing the database
|
||||
*/
|
||||
abstract void addAllValues(String name, Set<String> set) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the setting name which corresponds to the specified record.
|
||||
* @param record normalized settings record (name column is an integer index value)
|
||||
|
|
|
@ -16,8 +16,9 @@
|
|||
package ghidra.program.database.data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import db.*;
|
||||
import ghidra.util.ReadOnlyException;
|
||||
|
@ -110,6 +111,20 @@ class SettingsDBAdapterV0 extends SettingsDBAdapter {
|
|||
return list.toArray(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addAllValues(String name, Set<String> set) throws IOException {
|
||||
RecordIterator recIter = settingsTable.iterator();
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
if (name.equals(rec.getString(V0_SETTINGS_NAME_COL))) {
|
||||
String s = rec.getString(V0_SETTINGS_STRING_VALUE_COL);
|
||||
if (!StringUtils.isBlank(s)) {
|
||||
set.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSettingName(DBRecord normalizedRecord) {
|
||||
short nameIndex = normalizedRecord.getShortValue(SettingsDBAdapter.SETTINGS_NAME_INDEX_COL);
|
||||
|
|
|
@ -18,6 +18,8 @@ package ghidra.program.database.data;
|
|||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import db.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
|
@ -148,6 +150,24 @@ class SettingsDBAdapterV1 extends SettingsDBAdapter {
|
|||
return list.toArray(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addAllValues(String name, Set<String> set) throws IOException {
|
||||
short nameIndex = getNameIndex(name);
|
||||
if (nameIndex < MIN_NAME_INDEX) {
|
||||
return; // no such name defined
|
||||
}
|
||||
RecordIterator recIter = settingsTable.iterator();
|
||||
while (recIter.hasNext()) {
|
||||
DBRecord rec = recIter.next();
|
||||
if (nameIndex == rec.getShortValue(V1_SETTINGS_NAME_INDEX_COL)) {
|
||||
String s = rec.getString(V1_SETTINGS_STRING_VALUE_COL);
|
||||
if (!StringUtils.isBlank(s)) {
|
||||
set.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initNameMaps() throws IOException {
|
||||
if (nameIndexMap != null) {
|
||||
return;
|
||||
|
@ -250,6 +270,8 @@ class SettingsDBAdapterV1 extends SettingsDBAdapter {
|
|||
DBRecord updateSettingsRecord(long associationId, String name, String strValue, long longValue)
|
||||
throws IOException {
|
||||
|
||||
strValue = StringUtils.isBlank(strValue) ? null : strValue.trim();
|
||||
|
||||
DBRecord record = getSettingsRecord(associationId, name);
|
||||
if (record == null) {
|
||||
return createSettingsRecord(associationId, name, strValue, longValue);
|
||||
|
|
|
@ -15,10 +15,14 @@
|
|||
*/
|
||||
package ghidra.program.model.data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.docking.settings.StringSettingsDefinition;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
|
||||
public class AddressSpaceSettingsDefinition
|
||||
implements StringSettingsDefinition, TypeDefSettingsDefinition {
|
||||
|
@ -101,4 +105,30 @@ public class AddressSpaceSettingsDefinition
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSuggestedValues(Settings settings) {
|
||||
return settings.getSuggestedValues(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSuggestedValues() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPreferredValues(Object settingsOwner, Set<String> set) {
|
||||
if (settingsOwner instanceof ProgramBasedDataTypeManager) {
|
||||
ProgramBasedDataTypeManager dtm = (ProgramBasedDataTypeManager) settingsOwner;
|
||||
AddressFactory addressFactory = dtm.getProgram().getAddressFactory();
|
||||
for (AddressSpace space : addressFactory.getAllAddressSpaces()) {
|
||||
if (space.isLoadedMemorySpace()) {
|
||||
set.add(space.getName());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue