GP-1403 added suggested string setting support

This commit is contained in:
ghidra1 2022-03-23 09:52:38 -04:00
parent 3acd14c48a
commit 362bd6b5cb
11 changed files with 255 additions and 20 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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