mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
Merge remote-tracking branch 'origin/GP-5992-dragonmacher-options-age-off--SQUASHED'
This commit is contained in:
commit
e8ba7c3e45
10 changed files with 568 additions and 127 deletions
|
@ -24,6 +24,8 @@ import java.awt.event.KeyEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyEditorSupport;
|
import java.beans.PropertyEditorSupport;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
|
@ -34,7 +36,7 @@ import org.junit.*;
|
||||||
|
|
||||||
import generic.test.AbstractGuiTest;
|
import generic.test.AbstractGuiTest;
|
||||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.*;
|
||||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||||
import ghidra.util.exception.InvalidInputException;
|
import ghidra.util.exception.InvalidInputException;
|
||||||
import gui.event.MouseBinding;
|
import gui.event.MouseBinding;
|
||||||
|
@ -454,12 +456,133 @@ public class OptionsTest extends AbstractGuiTest {
|
||||||
assertTrue(options.contains("Foo"));
|
assertTrue(options.contains("Foo"));
|
||||||
assertTrue(options.contains("Bar"));
|
assertTrue(options.contains("Bar"));
|
||||||
|
|
||||||
options.registerOption("Bar", 0, null, "foo");
|
options.registerOption("Bar", 0, null, "Bar description");
|
||||||
|
|
||||||
options.removeUnusedOptions();
|
options.removeUnusedOptions();
|
||||||
|
|
||||||
assertFalse(options.contains("Foo"));
|
// registered; should stay around
|
||||||
assertTrue(options.contains("Bar"));
|
assertTrue(options.contains("Bar"));
|
||||||
|
// not registered, but not aged-off; should stay around
|
||||||
|
assertTrue(options.contains("Foo"));
|
||||||
|
|
||||||
|
// Save, edit the date of the options to be older than 1 year, which is our age cutoff.
|
||||||
|
// Make sure any unregistered option is then removed.
|
||||||
|
Element savedRoot = options.getXmlRoot(false);
|
||||||
|
LocalDate twoYearsAgo = LocalDate.now().minusYears(2);
|
||||||
|
Set<String> optionNames = Set.of("Bar", "Foo");
|
||||||
|
setLastUsedDate(savedRoot, twoYearsAgo, optionNames);
|
||||||
|
|
||||||
|
options = new ToolOptions(savedRoot);
|
||||||
|
assertTrue(options.contains("Foo"));
|
||||||
|
assertTrue(options.contains("Bar"));
|
||||||
|
|
||||||
|
options.registerOption("Bar", 0, null, "Bar description");
|
||||||
|
options.removeUnusedOptions();
|
||||||
|
|
||||||
|
// registered; should stay around
|
||||||
|
assertTrue(options.contains("Bar"));
|
||||||
|
// not registered, and older than 1 year; should be removed
|
||||||
|
assertFalse(options.contains("Foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveUnusedOption_WrappedOption() throws Exception {
|
||||||
|
|
||||||
|
Date date = new Date();
|
||||||
|
options.setDate("Foo", date);
|
||||||
|
options.setDate("Bar", date);
|
||||||
|
saveAndRestoreOptions();
|
||||||
|
assertEquals(date, options.getDate("Foo", null));
|
||||||
|
assertEquals(date, options.getDate("Bar", null));
|
||||||
|
|
||||||
|
Date defaultDate = new SimpleDateFormat("yyyy-MM-dd").parse("2025-09-12");
|
||||||
|
|
||||||
|
options.registerOption("Bar", defaultDate, null, "Bar description");
|
||||||
|
options.removeUnusedOptions();
|
||||||
|
|
||||||
|
// registered; should stay around
|
||||||
|
assertTrue(options.contains("Bar"));
|
||||||
|
// not registered, but not aged-off; should stay around
|
||||||
|
assertTrue(options.contains("Foo"));
|
||||||
|
|
||||||
|
// Save, edit the date of the options to be older than 1 year, which is our age cutoff.
|
||||||
|
// Make sure any unregistered option is then removed.
|
||||||
|
Element savedRoot = options.getXmlRoot(false);
|
||||||
|
LocalDate twoYearsAgo = LocalDate.now().minusYears(2);
|
||||||
|
Set<String> optionNames = Set.of("Bar", "Foo");
|
||||||
|
setLastUsedDate(savedRoot, twoYearsAgo, optionNames);
|
||||||
|
|
||||||
|
options = new ToolOptions(savedRoot);
|
||||||
|
assertTrue(options.contains("Foo"));
|
||||||
|
assertTrue(options.contains("Bar"));
|
||||||
|
|
||||||
|
options.registerOption("Bar", defaultDate, null, "Bar description");
|
||||||
|
options.removeUnusedOptions();
|
||||||
|
|
||||||
|
// registered; should stay around
|
||||||
|
assertTrue(options.contains("Bar"));
|
||||||
|
// not registered, and older than 1 year; should be removed
|
||||||
|
assertFalse(options.contains("Foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnregisteredWarningMessage() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test that options access, but not registered, will trigger a warning message.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SpyErrorLogger spyLogger = new SpyErrorLogger();
|
||||||
|
Msg.setErrorLogger(spyLogger);
|
||||||
|
|
||||||
|
options.getInt("Foo", 1);
|
||||||
|
assertTrue(spyLogger.isEmtpy());
|
||||||
|
|
||||||
|
options.validateOptions();
|
||||||
|
spyLogger.assertLogMessage("Unregistered", "property", "Foo");
|
||||||
|
|
||||||
|
spyLogger.reset();
|
||||||
|
options.registerOption("Foo", 0, null, "Description");
|
||||||
|
options.getInt("Foo", 2);
|
||||||
|
options.validateOptions();
|
||||||
|
assertTrue(spyLogger.isEmtpy());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLastUsedDate(Element savedRoot, LocalDate lastRegisteredDate,
|
||||||
|
Set<String> names) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Msg.debug(this, XmlUtilities.toString(savedRoot));
|
||||||
|
|
||||||
|
<CATEGORY NAME="Test">
|
||||||
|
<SAVE_STATE NAME="Bar" TYPE="int" VALUE="3" LAST_REGISTERED="2025-09-17" />
|
||||||
|
<SAVE_STATE NAME="Bar" TYPE="boolean" VALUE="false" LAST_REGISTERED="2025-09-17" />
|
||||||
|
</CATEGORY>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
String dateString = lastRegisteredDate.format(ToolOptions.LAST_REGISTERED_DATE_FORMATTER);
|
||||||
|
|
||||||
|
List<Element> elements = getElementsByName(savedRoot, names);
|
||||||
|
for (Element element : elements) { // <SAVE_STATE NAME="Foo" ...>
|
||||||
|
element.setAttribute(ToolOptions.LAST_REGISTERED_DATE_ATTIBUTE, dateString);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private List<Element> getElementsByName(Element savedRoot, Set<String> names) {
|
||||||
|
|
||||||
|
List<Element> matches = new ArrayList<>();
|
||||||
|
List<Element> children = savedRoot.getChildren();
|
||||||
|
for (Element saveState : children) {
|
||||||
|
String name = saveState.getAttributeValue("NAME");
|
||||||
|
if (names.contains(name)) {
|
||||||
|
matches.add(saveState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -538,7 +538,7 @@ public class DecompileOptions {
|
||||||
|
|
||||||
grabFromProgram(program);
|
grabFromProgram(program);
|
||||||
|
|
||||||
// assuming if one is not registered, then none area
|
// assuming if one is not registered, then none are
|
||||||
if (!opt.isRegistered(PREDICATE_OPTIONSTRING)) {
|
if (!opt.isRegistered(PREDICATE_OPTIONSTRING)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/* ###
|
||||||
|
* 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.framework.options;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.jdom.Attribute;
|
||||||
|
import org.jdom.Element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link SaveState} that allows clients to add attributes to properties in this save
|
||||||
|
* state. The following code shows how to use this class:
|
||||||
|
* <pre>
|
||||||
|
* AttributedSaveState ss = new AttributedSaveState();
|
||||||
|
* ss.putBoolean("Happy", true);
|
||||||
|
*
|
||||||
|
* Map<String, String> attrs = Map.of("MyAttribute", "AttributeValue");
|
||||||
|
* ss.addAttrbibutes("Happy", attrs);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>In this example, the property "Happy" will be given the attribute "MyAttribute" with the value
|
||||||
|
* of "AttributeValue". This is useful for clients that wish to add attributes to individual
|
||||||
|
* properties, such as a date for tracking usage.
|
||||||
|
*
|
||||||
|
* <p><u>Usage Note:</u> The given attributes are only supported when writing and reading xml. Json
|
||||||
|
* is not supported.
|
||||||
|
*/
|
||||||
|
public class AttributedSaveState extends SaveState {
|
||||||
|
|
||||||
|
private Map<String, Map<String, String>> propertyAttributes;
|
||||||
|
|
||||||
|
public AttributedSaveState() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedSaveState(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AttributedSaveState(Element root) {
|
||||||
|
super(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given map of attribute name/value pairs to this save state.
|
||||||
|
* @param propertyName the property name within this save state that will be attributed
|
||||||
|
* @param attributes the attributes
|
||||||
|
*/
|
||||||
|
public void addAttributes(String propertyName, Map<String, String> attributes) {
|
||||||
|
getPropertyAttributes().put(propertyName, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all attributes associated with the given property name.
|
||||||
|
* @param propertyName the property name within this save state that has the given attributes
|
||||||
|
*/
|
||||||
|
public void removeAttributes(String propertyName) {
|
||||||
|
getPropertyAttributes().remove(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the attributes currently associated with the given property name
|
||||||
|
* @param propertyName the property name for which to get attributes
|
||||||
|
* @return the attributes or null
|
||||||
|
*/
|
||||||
|
public Map<String, String> getAttributes(String propertyName) {
|
||||||
|
return getPropertyAttributes().get(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Map<String, String>> getPropertyAttributes() {
|
||||||
|
if (propertyAttributes == null) {
|
||||||
|
propertyAttributes = new HashMap<>();
|
||||||
|
}
|
||||||
|
return propertyAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SaveState createSaveState() {
|
||||||
|
return new AttributedSaveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initializeElement(Element e) {
|
||||||
|
|
||||||
|
String name = e.getAttributeValue(NAME);
|
||||||
|
if (name == null) {
|
||||||
|
return; // sub-element; properties not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Overridden to add our attributes to the newly created element, used to create xml
|
||||||
|
//
|
||||||
|
Map<String, String> attrs = getPropertyAttributes().get(name);
|
||||||
|
if (attrs != null) {
|
||||||
|
Set<Entry<String, String>> entries = attrs.entrySet();
|
||||||
|
for (Entry<String, String> entry : entries) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
String value = entry.getValue();
|
||||||
|
e.setAttribute(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processElement(Element element) {
|
||||||
|
super.processElement(element);
|
||||||
|
|
||||||
|
String name = element.getAttributeValue(NAME);
|
||||||
|
if (name == null) {
|
||||||
|
return; // sub-element; properties not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Overridden to extract non-standard attributes from the given element. The element was
|
||||||
|
// created after restoring from xml. We extract the attributes that we added above in
|
||||||
|
// initializeElement().
|
||||||
|
//
|
||||||
|
Map<String, String> newAttrs = new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Attribute> attrs = element.getAttributes();
|
||||||
|
for (Attribute attr : attrs) {
|
||||||
|
|
||||||
|
String attrName = attr.getName();
|
||||||
|
String attrValue = switch (attrName) {
|
||||||
|
// ignore standard attributes, as they are managed by the parent class
|
||||||
|
case NAME, TYPE, VALUE -> null;
|
||||||
|
default -> attr.getValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (attrValue != null) {
|
||||||
|
newAttrs.put(attrName, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newAttrs.isEmpty()) {
|
||||||
|
getPropertyAttributes().put(name, newAttrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,7 +78,7 @@ public class GProperties {
|
||||||
private static final String STATE = "STATE";
|
private static final String STATE = "STATE";
|
||||||
protected static final String TYPE = "TYPE";
|
protected static final String TYPE = "TYPE";
|
||||||
protected static final String NAME = "NAME";
|
protected static final String NAME = "NAME";
|
||||||
private static final String VALUE = "VALUE";
|
protected static final String VALUE = "VALUE";
|
||||||
public static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
public static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||||
private static final String ARRAY_ELEMENT_NAME = "A";
|
private static final String ARRAY_ELEMENT_NAME = "A";
|
||||||
protected TreeMap<String, Object> map; // use ordered map for deterministic serialization
|
protected TreeMap<String, Object> map; // use ordered map for deterministic serialization
|
||||||
|
@ -505,35 +505,34 @@ public class GProperties {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Element createElement(String key, Object value) {
|
protected Element createElement(String propertyName, Object value) {
|
||||||
Element elem = null;
|
Element elem = null;
|
||||||
if (value instanceof Element) {
|
if (value instanceof Element) {
|
||||||
elem = createElementFromElement(key, (Element) value);
|
elem = createElementFromElement(propertyName, (Element) value);
|
||||||
}
|
}
|
||||||
else if (value instanceof Byte) {
|
else if (value instanceof Byte) {
|
||||||
elem = setAttributes(key, "byte", ((Byte) value).toString());
|
elem = setAttributes(propertyName, "byte", ((Byte) value).toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof Short) {
|
else if (value instanceof Short) {
|
||||||
elem = setAttributes(key, "short", ((Short) value).toString());
|
elem = setAttributes(propertyName, "short", ((Short) value).toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof Integer) {
|
else if (value instanceof Integer) {
|
||||||
elem = setAttributes(key, "int", ((Integer) value).toString());
|
elem = setAttributes(propertyName, "int", ((Integer) value).toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof Long) {
|
else if (value instanceof Long) {
|
||||||
elem = setAttributes(key, "long", ((Long) value).toString());
|
elem = setAttributes(propertyName, "long", ((Long) value).toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof Float) {
|
else if (value instanceof Float) {
|
||||||
elem = setAttributes(key, "float", ((Float) value).toString());
|
elem = setAttributes(propertyName, "float", ((Float) value).toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof Double) {
|
else if (value instanceof Double) {
|
||||||
elem = setAttributes(key, "double", ((Double) value).toString());
|
elem = setAttributes(propertyName, "double", ((Double) value).toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof Boolean) {
|
else if (value instanceof Boolean) {
|
||||||
elem = setAttributes(key, "boolean", ((Boolean) value).toString());
|
elem = setAttributes(propertyName, "boolean", ((Boolean) value).toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof String) {
|
else if (value instanceof String) {
|
||||||
elem = new Element(STATE);
|
elem = createElement(STATE, propertyName);
|
||||||
elem.setAttribute(NAME, key);
|
|
||||||
elem.setAttribute(TYPE, "string");
|
elem.setAttribute(TYPE, "string");
|
||||||
if (XmlUtilities.hasInvalidXMLCharacters((String) value)) {
|
if (XmlUtilities.hasInvalidXMLCharacters((String) value)) {
|
||||||
elem.setAttribute("ENCODED_VALUE", NumericUtilities
|
elem.setAttribute("ENCODED_VALUE", NumericUtilities
|
||||||
|
@ -544,71 +543,66 @@ public class GProperties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (value instanceof Color) {
|
else if (value instanceof Color) {
|
||||||
elem = setAttributes(key, "Color", Integer.toString(((Color) value).getRGB()));
|
elem = setAttributes(propertyName, "Color", Integer.toString(((Color) value).getRGB()));
|
||||||
}
|
}
|
||||||
else if (value instanceof Date) {
|
else if (value instanceof Date) {
|
||||||
elem = setAttributes(key, "Date", DATE_FORMAT.format((Date) value));
|
elem = setAttributes(propertyName, "Date", DATE_FORMAT.format((Date) value));
|
||||||
}
|
}
|
||||||
else if (value instanceof File) {
|
else if (value instanceof File) {
|
||||||
elem = setAttributes(key, "File", ((File) value).getAbsolutePath());
|
elem = setAttributes(propertyName, "File", ((File) value).getAbsolutePath());
|
||||||
}
|
}
|
||||||
else if (value instanceof KeyStroke) {
|
else if (value instanceof KeyStroke) {
|
||||||
elem = setAttributes(key, "KeyStroke", value.toString());
|
elem = setAttributes(propertyName, "KeyStroke", value.toString());
|
||||||
}
|
}
|
||||||
else if (value instanceof Font font) {
|
else if (value instanceof Font font) {
|
||||||
elem = setAttributes(key, "Font", toFontString(font));
|
elem = setAttributes(propertyName, "Font", toFontString(font));
|
||||||
}
|
}
|
||||||
else if (value instanceof byte[]) {
|
else if (value instanceof byte[]) {
|
||||||
elem = new Element("BYTES");
|
elem = createElement("BYTES", propertyName);
|
||||||
elem.setAttribute(NAME, key);
|
|
||||||
elem.setAttribute(VALUE, NumericUtilities.convertBytesToString((byte[]) value));
|
elem.setAttribute(VALUE, NumericUtilities.convertBytesToString((byte[]) value));
|
||||||
}
|
}
|
||||||
else if (value instanceof short[]) {
|
else if (value instanceof short[]) {
|
||||||
elem = setArrayAttributes(key, "short", value);
|
elem = setArrayAttributes(propertyName, "short", value);
|
||||||
}
|
}
|
||||||
else if (value instanceof int[]) {
|
else if (value instanceof int[]) {
|
||||||
elem = setArrayAttributes(key, "int", value);
|
elem = setArrayAttributes(propertyName, "int", value);
|
||||||
}
|
}
|
||||||
else if (value instanceof long[]) {
|
else if (value instanceof long[]) {
|
||||||
elem = setArrayAttributes(key, "long", value);
|
elem = setArrayAttributes(propertyName, "long", value);
|
||||||
}
|
}
|
||||||
else if (value instanceof float[]) {
|
else if (value instanceof float[]) {
|
||||||
elem = setArrayAttributes(key, "float", value);
|
elem = setArrayAttributes(propertyName, "float", value);
|
||||||
}
|
}
|
||||||
else if (value instanceof double[]) {
|
else if (value instanceof double[]) {
|
||||||
elem = setArrayAttributes(key, "double", value);
|
elem = setArrayAttributes(propertyName, "double", value);
|
||||||
}
|
}
|
||||||
else if (value instanceof boolean[]) {
|
else if (value instanceof boolean[]) {
|
||||||
elem = setArrayAttributes(key, "boolean", value);
|
elem = setArrayAttributes(propertyName, "boolean", value);
|
||||||
}
|
}
|
||||||
else if (value instanceof String[]) {
|
else if (value instanceof String[]) {
|
||||||
elem = setArrayAttributes(key, "string", value);
|
elem = setArrayAttributes(propertyName, "string", value);
|
||||||
}
|
}
|
||||||
else if (value instanceof Enum) {
|
else if (value instanceof Enum) {
|
||||||
Enum<?> e = (Enum<?>) value;
|
Enum<?> e = (Enum<?>) value;
|
||||||
elem = new Element("ENUM");
|
elem = createElement("ENUM", propertyName);
|
||||||
elem.setAttribute(NAME, key);
|
|
||||||
elem.setAttribute(TYPE, "enum");
|
elem.setAttribute(TYPE, "enum");
|
||||||
elem.setAttribute("CLASS", e.getClass().getName());
|
elem.setAttribute("CLASS", e.getClass().getName());
|
||||||
elem.setAttribute(VALUE, e.name());
|
elem.setAttribute(VALUE, e.name());
|
||||||
}
|
}
|
||||||
else if (value instanceof GProperties) {
|
else if (value instanceof GProperties) {
|
||||||
Element savedElement = ((GProperties) value).saveToXml();
|
Element savedElement = ((GProperties) value).saveToXml();
|
||||||
elem = new Element(GPROPERTIES_TAG);
|
elem = createElement(GPROPERTIES_TAG, propertyName);
|
||||||
elem.setAttribute(NAME, key);
|
|
||||||
elem.setAttribute(TYPE, G_PROPERTIES_TYPE);
|
elem.setAttribute(TYPE, G_PROPERTIES_TYPE);
|
||||||
elem.addContent(savedElement);
|
elem.addContent(savedElement);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
elem = new Element("NULL");
|
elem = createElement("NULL", propertyName);
|
||||||
elem.setAttribute(NAME, key);
|
|
||||||
}
|
}
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> Element setArrayAttributes(String key, String type, Object values) {
|
private <T> Element setArrayAttributes(String propertyName, String type, Object values) {
|
||||||
Element elem = new Element("ARRAY");
|
Element elem = createElement("ARRAY", propertyName);
|
||||||
elem.setAttribute(NAME, key);
|
|
||||||
elem.setAttribute(TYPE, type);
|
elem.setAttribute(TYPE, type);
|
||||||
for (int i = 0; i < Array.getLength(values); i++) {
|
for (int i = 0; i < Array.getLength(values); i++) {
|
||||||
Object value = Array.get(values, i);
|
Object value = Array.get(values, i);
|
||||||
|
@ -621,10 +615,19 @@ public class GProperties {
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Element setAttributes(String key, String type, String value) {
|
protected Element createElement(String tag, String name) {
|
||||||
Element elem;
|
Element e = new Element(tag);
|
||||||
elem = new Element(STATE);
|
e.setAttribute(NAME, name);
|
||||||
elem.setAttribute(NAME, key);
|
initializeElement(e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initializeElement(Element e) {
|
||||||
|
// subclasses may override
|
||||||
|
}
|
||||||
|
|
||||||
|
private Element setAttributes(String propertyName, String type, String value) {
|
||||||
|
Element elem = createElement(STATE, propertyName);
|
||||||
elem.setAttribute(TYPE, type);
|
elem.setAttribute(TYPE, type);
|
||||||
elem.setAttribute(VALUE, value);
|
elem.setAttribute(VALUE, value);
|
||||||
return elem;
|
return elem;
|
||||||
|
@ -792,8 +795,7 @@ public class GProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Element createElementFromElement(String internalKey, Element internalElement) {
|
protected Element createElementFromElement(String internalKey, Element internalElement) {
|
||||||
Element newElement = new Element("XML");
|
Element newElement = createElement("XML", internalKey);
|
||||||
newElement.setAttribute(NAME, internalKey);
|
|
||||||
|
|
||||||
Element internalElementClone = (Element) internalElement.clone();
|
Element internalElementClone = (Element) internalElement.clone();
|
||||||
newElement.addContent(internalElementClone);
|
newElement.addContent(internalElementClone);
|
||||||
|
@ -1420,11 +1422,11 @@ public class GProperties {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toFontString(Font font) {
|
private static String toFontString(Font font) {
|
||||||
return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
|
return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getStyleString(Font font) {
|
private static String getStyleString(Font font) {
|
||||||
boolean bold = font.isBold();
|
boolean bold = font.isBold();
|
||||||
boolean italic = font.isItalic();
|
boolean italic = font.isItalic();
|
||||||
if (bold && italic) {
|
if (bold && italic) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.framework.options;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
|
@ -114,31 +115,86 @@ public class SaveState extends XmlProperties {
|
||||||
return getAsType(name, null, SaveState.class);
|
return getAsType(name, null, SaveState.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
protected void processElement(Element element) {
|
protected void processElement(Element element) {
|
||||||
String tag = element.getName();
|
String tag = element.getName();
|
||||||
|
if (!tag.equals("SAVE_STATE")) {
|
||||||
if (tag.equals("SAVE_STATE")) {
|
super.processElement(element);
|
||||||
Element child = (Element) element.getChildren().get(0);
|
|
||||||
if (child != null) {
|
|
||||||
String name = element.getAttributeValue(NAME);
|
|
||||||
map.put(name, new SaveState(child));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
super.processElement(element);
|
/*
|
||||||
|
When using a SaveState inside of a SaveState, we produce xml that looks like this:
|
||||||
|
|
||||||
|
<SAVE_STATE NAME="Bar" TYPE="SaveState">
|
||||||
|
<STATE NAME="Bar" TYPE="int" VALUE="3" />
|
||||||
|
</SAVE_STATE>
|
||||||
|
*/
|
||||||
|
|
||||||
|
SaveState saveState = createSaveState();
|
||||||
|
|
||||||
|
List<Element> children = element.getChildren();
|
||||||
|
if (children.isEmpty()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Element child = (Element) element.getChildren().get(0);
|
||||||
|
String childTag = child.getName();
|
||||||
|
if (childTag.equals("SAVE_STATE")) {
|
||||||
|
/*
|
||||||
|
Old style tag, with one level of extra nesting
|
||||||
|
|
||||||
|
<SAVE_STATE NAME="Bar" TYPE="SaveState">
|
||||||
|
<SAVE_STATE>
|
||||||
|
<STATE NAME="DATED_OPTION" TYPE="int" VALUE="3" />
|
||||||
|
</SAVE_STATE>
|
||||||
|
</SAVE_STATE>
|
||||||
|
|
||||||
|
*/
|
||||||
|
children = child.getChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Element e : children) {
|
||||||
|
saveState.processElement(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String parentName = element.getAttributeValue(NAME);
|
||||||
|
map.put(parentName, saveState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
protected Element createElement(String key, Object value) {
|
protected Element createElement(String key, Object value) {
|
||||||
if (value instanceof SaveState saveState) {
|
if (!(value instanceof SaveState saveState)) {
|
||||||
|
return super.createElement(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
When using a SaveState inside of a SaveState, we produce xml that looks like this:
|
||||||
|
|
||||||
|
<SAVE_STATE NAME="Bar" TYPE="SaveState">
|
||||||
|
<STATE NAME="Bar" TYPE="int" VALUE="3" />
|
||||||
|
</SAVE_STATE>
|
||||||
|
*/
|
||||||
|
|
||||||
Element savedElement = saveState.saveToXml();
|
Element savedElement = saveState.saveToXml();
|
||||||
Element element = new Element("SAVE_STATE");
|
Element element = new Element("SAVE_STATE");
|
||||||
element.setAttribute(NAME, key);
|
element.setAttribute(NAME, key);
|
||||||
element.setAttribute(TYPE, "SaveState");
|
element.setAttribute(TYPE, "SaveState");
|
||||||
element.addContent(savedElement);
|
|
||||||
|
// do not write an extra <SAVE_STATE> intermediate node
|
||||||
|
List<Element> children = savedElement.getChildren();
|
||||||
|
for (Element e : children) {
|
||||||
|
Element newElement = (Element) e.clone();
|
||||||
|
element.addContent(newElement);
|
||||||
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
return super.createElement(key, value);
|
|
||||||
|
// allows subclasses to override how sub-save states are created
|
||||||
|
protected SaveState createSaveState() {
|
||||||
|
return new SaveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.util;
|
package ghidra.util;
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -88,6 +88,10 @@ public class SpyErrorLogger implements ErrorLogger, Iterable<String> {
|
||||||
messages.clear();
|
messages.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmtpy() {
|
||||||
|
return messages.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public void assertLogMessage(String... words) {
|
public void assertLogMessage(String... words) {
|
||||||
for (String message : this) {
|
for (String message : this) {
|
||||||
if (StringUtilities.containsAllIgnoreCase(message, words)) {
|
if (StringUtilities.containsAllIgnoreCase(message, words)) {
|
||||||
|
|
|
@ -260,8 +260,11 @@ public abstract class AbstractOptions implements Options {
|
||||||
if (option == null) {
|
if (option == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (option.getOptionType() != type) {
|
OptionType existingType = option.getOptionType();
|
||||||
Msg.error(this, "Registered option incompatible with existing option: " + optionName,
|
if (existingType != type) {
|
||||||
|
Msg.error(this, "Registered option incompatible with existing option: '%s'. " +
|
||||||
|
"Existing type '%s'; registered type '%s'.".formatted(optionName, existingType,
|
||||||
|
type),
|
||||||
new AssertException());
|
new AssertException());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.framework.options;
|
package ghidra.framework.options;
|
||||||
|
|
||||||
import java.beans.PropertyEditor;
|
import java.beans.PropertyEditor;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
@ -23,11 +24,15 @@ import ghidra.util.SystemUtilities;
|
||||||
import utilities.util.reflection.ReflectionUtilities;
|
import utilities.util.reflection.ReflectionUtilities;
|
||||||
|
|
||||||
public abstract class Option {
|
public abstract class Option {
|
||||||
|
|
||||||
public static final String UNREGISTERED_OPTION = "Unregistered Option";
|
public static final String UNREGISTERED_OPTION = "Unregistered Option";
|
||||||
|
|
||||||
|
private static final LocalDate ONE_YEAR_AGO = LocalDate.now().minusYears(1);
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private Object defaultValue;
|
private Object defaultValue;
|
||||||
private boolean isRegistered;
|
private boolean isRegistered;
|
||||||
|
private LocalDate lastRegisteredDate;
|
||||||
private String description;
|
private String description;
|
||||||
private HelpLocation helpLocation;
|
private HelpLocation helpLocation;
|
||||||
private OptionType optionType;
|
private OptionType optionType;
|
||||||
|
@ -45,7 +50,10 @@ public abstract class Option {
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
this.isRegistered = isRegistered;
|
this.isRegistered = isRegistered;
|
||||||
this.propertyEditor = editor;
|
this.propertyEditor = editor;
|
||||||
if (!isRegistered) {
|
if (isRegistered) {
|
||||||
|
lastRegisteredDate = LocalDate.now();
|
||||||
|
}
|
||||||
|
else {
|
||||||
recordInception();
|
recordInception();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +75,7 @@ public abstract class Option {
|
||||||
defaultValue = defaultValue != null ? defaultValue : updatedDefaultValue;
|
defaultValue = defaultValue != null ? defaultValue : updatedDefaultValue;
|
||||||
propertyEditor = propertyEditor != null ? propertyEditor : updatedEditor;
|
propertyEditor = propertyEditor != null ? propertyEditor : updatedEditor;
|
||||||
isRegistered = true;
|
isRegistered = true;
|
||||||
|
lastRegisteredDate = LocalDate.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Object getCurrentValue();
|
public abstract Object getCurrentValue();
|
||||||
|
@ -74,7 +83,8 @@ public abstract class Option {
|
||||||
public abstract void doSetCurrentValue(Object value);
|
public abstract void doSetCurrentValue(Object value);
|
||||||
|
|
||||||
public void setCurrentValue(Object value) {
|
public void setCurrentValue(Object value) {
|
||||||
this.isRegistered = true;
|
isRegistered = true;
|
||||||
|
lastRegisteredDate = LocalDate.now();
|
||||||
doSetCurrentValue(value);
|
doSetCurrentValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +119,44 @@ public abstract class Option {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean wasRegisteredInPreviousSession() {
|
||||||
|
return lastRegisteredDate != null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRegistered() {
|
public boolean isRegistered() {
|
||||||
return isRegistered;
|
return isRegistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLastRegisteredDate(LocalDate date) {
|
||||||
|
lastRegisteredDate = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getLastRegisteredDate() {
|
||||||
|
LocalDate date = lastRegisteredDate;
|
||||||
|
if (date == null) {
|
||||||
|
// This implies a new option that has not been registered and was not in a saved tool.
|
||||||
|
// Pick an old date so the option will not linger.
|
||||||
|
date = ONE_YEAR_AGO;
|
||||||
|
}
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the last registered date for this option is older than 1 year ago. That
|
||||||
|
* means that the option has not been registered and is likely no longer valid. This may not be
|
||||||
|
* true, if the given option still exists, but is only active on-demand by the user. If the
|
||||||
|
* option has expired, it will be removed. If it is still an existing on-demand option, it can
|
||||||
|
* again be saved when the user loads the owning provider and changes the option. In this case,
|
||||||
|
* it will remain in the tool for at least another year.
|
||||||
|
* @return true if expired
|
||||||
|
*/
|
||||||
|
public boolean hasExpired() {
|
||||||
|
if (lastRegisteredDate == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return lastRegisteredDate.isBefore(ONE_YEAR_AGO);
|
||||||
|
}
|
||||||
|
|
||||||
public void restoreDefault() {
|
public void restoreDefault() {
|
||||||
setCurrentValue(defaultValue);
|
setCurrentValue(defaultValue);
|
||||||
}
|
}
|
||||||
|
@ -151,4 +195,5 @@ public abstract class Option {
|
||||||
public OptionType getOptionType() {
|
public OptionType getOptionType() {
|
||||||
return optionType;
|
return optionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.awt.Font;
|
||||||
import java.beans.PropertyEditor;
|
import java.beans.PropertyEditor;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
@ -45,10 +47,16 @@ import ghidra.util.exception.AssertException;
|
||||||
* <p>The Options Dialog shows the delimited hierarchy in tree format.
|
* <p>The Options Dialog shows the delimited hierarchy in tree format.
|
||||||
*/
|
*/
|
||||||
public class ToolOptions extends AbstractOptions {
|
public class ToolOptions extends AbstractOptions {
|
||||||
|
|
||||||
private static final String CLASS_ATTRIBUTE = "CLASS";
|
private static final String CLASS_ATTRIBUTE = "CLASS";
|
||||||
private static final String NAME_ATTRIBUTE = "NAME";
|
private static final String NAME_ATTRIBUTE = "NAME";
|
||||||
private static final String WRAPPED_OPTION_NAME = "WRAPPED_OPTION";
|
private static final String WRAPPED_OPTION_NAME = "WRAPPED_OPTION";
|
||||||
private static final String CLEARED_VALUE_ELEMENT_NAME = "CLEARED_VALUE";
|
private static final String CLEARED_VALUE_ELEMENT_NAME = "CLEARED_VALUE";
|
||||||
|
|
||||||
|
public static final String LAST_REGISTERED_DATE_ATTIBUTE = "LAST_REGISTERED";
|
||||||
|
public static final DateTimeFormatter LAST_REGISTERED_DATE_FORMATTER =
|
||||||
|
DateTimeFormatter.ISO_LOCAL_DATE;
|
||||||
|
|
||||||
public static final Set<Class<?>> PRIMITIVE_CLASSES = buildPrimitiveClassSet();
|
public static final Set<Class<?>> PRIMITIVE_CLASSES = buildPrimitiveClassSet();
|
||||||
public static final Set<Class<?>> WRAPPABLE_CLASSES = buildWrappableClassSet();
|
public static final Set<Class<?>> WRAPPABLE_CLASSES = buildWrappableClassSet();
|
||||||
|
|
||||||
|
@ -91,7 +99,7 @@ public class ToolOptions extends AbstractOptions {
|
||||||
public ToolOptions(Element root) {
|
public ToolOptions(Element root) {
|
||||||
this(root.getAttributeValue(NAME_ATTRIBUTE));
|
this(root.getAttributeValue(NAME_ATTRIBUTE));
|
||||||
|
|
||||||
SaveState saveState = new SaveState(root);
|
AttributedSaveState saveState = new AttributedSaveState(root);
|
||||||
|
|
||||||
readNonWrappedOptions(saveState);
|
readNonWrappedOptions(saveState);
|
||||||
|
|
||||||
|
@ -103,12 +111,23 @@ public class ToolOptions extends AbstractOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readNonWrappedOptions(SaveState saveState) {
|
private void readNonWrappedOptions(AttributedSaveState saveState) {
|
||||||
for (String optionName : saveState.getNames()) {
|
for (String optionName : saveState.getNames()) {
|
||||||
Object object = saveState.getObject(optionName);
|
Object object = saveState.getObject(optionName);
|
||||||
Option option =
|
|
||||||
createUnregisteredOption(optionName, OptionType.getOptionType(object), null);
|
// Get the last registered date. No date implies an old xml format.
|
||||||
|
LocalDate date = LocalDate.now();
|
||||||
|
Map<String, String> attrs = saveState.getAttributes(optionName);
|
||||||
|
String dateString = attrs.get(LAST_REGISTERED_DATE_ATTIBUTE);
|
||||||
|
if (dateString != null) {
|
||||||
|
date = LocalDate.parse(dateString, LAST_REGISTERED_DATE_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionType type = OptionType.getOptionType(object);
|
||||||
|
Option option = createUnregisteredOption(optionName, type, null);
|
||||||
option.doSetCurrentValue(object); // use doSet versus set so that it is not registered
|
option.doSetCurrentValue(object); // use doSet versus set so that it is not registered
|
||||||
|
option.setLastRegisteredDate(date);
|
||||||
|
|
||||||
valueMap.put(optionName, option);
|
valueMap.put(optionName, option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,19 +143,19 @@ public class ToolOptions extends AbstractOptions {
|
||||||
continue; // shouldn't happen
|
continue; // shouldn't happen
|
||||||
}
|
}
|
||||||
|
|
||||||
String optionName = element.getAttributeValue(NAME_ATTRIBUTE);
|
|
||||||
Class<?> c = Class.forName(element.getAttributeValue(CLASS_ATTRIBUTE));
|
Class<?> c = Class.forName(element.getAttributeValue(CLASS_ATTRIBUTE));
|
||||||
Constructor<?> constructor = c.getDeclaredConstructor();
|
Constructor<?> constructor = c.getDeclaredConstructor();
|
||||||
WrappedOption wo = (WrappedOption) constructor.newInstance();
|
WrappedOption wo = (WrappedOption) constructor.newInstance();
|
||||||
wo.readState(new SaveState(element));
|
wo.readState(new SaveState(element));
|
||||||
|
|
||||||
if (wo instanceof WrappedCustomOption wrappedCustom && !wrappedCustom.isValid()) {
|
if (wo instanceof WrappedCustomOption wrappedCustom && !wrappedCustom.isValid()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wo instanceof WrappedKeyStroke wrappedKs) {
|
if (wo instanceof WrappedKeyStroke wrappedKs) {
|
||||||
wo = wrappedKs.toWrappedActionTrigger();
|
wo = wrappedKs.toWrappedActionTrigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String optionName = element.getAttributeValue(NAME_ATTRIBUTE);
|
||||||
Option option = createUnregisteredOption(optionName, wo.getOptionType(), null);
|
Option option = createUnregisteredOption(optionName, wo.getOptionType(), null);
|
||||||
valueMap.put(optionName, option);
|
valueMap.put(optionName, option);
|
||||||
|
|
||||||
|
@ -149,6 +168,15 @@ public class ToolOptions extends AbstractOptions {
|
||||||
else {
|
else {
|
||||||
option.doSetCurrentValue(wo.getObject()); // use doSet so that it is not registered
|
option.doSetCurrentValue(wo.getObject()); // use doSet so that it is not registered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the last registered date. No date implies an old xml format.
|
||||||
|
LocalDate date = LocalDate.now();
|
||||||
|
String dateString = element.getAttributeValue(LAST_REGISTERED_DATE_ATTIBUTE);
|
||||||
|
if (dateString != null) {
|
||||||
|
date = LocalDate.parse(dateString, LAST_REGISTERED_DATE_FORMATTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
option.setLastRegisteredDate(date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +190,7 @@ public class ToolOptions extends AbstractOptions {
|
||||||
*/
|
*/
|
||||||
public Element getXmlRoot(boolean includeDefaultBindings) {
|
public Element getXmlRoot(boolean includeDefaultBindings) {
|
||||||
|
|
||||||
SaveState saveState = new SaveState(XML_ELEMENT_NAME);
|
AttributedSaveState saveState = new AttributedSaveState(XML_ELEMENT_NAME);
|
||||||
|
|
||||||
writeNonWrappedOptions(includeDefaultBindings, saveState);
|
writeNonWrappedOptions(includeDefaultBindings, saveState);
|
||||||
|
|
||||||
|
@ -174,16 +202,29 @@ public class ToolOptions extends AbstractOptions {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeNonWrappedOptions(boolean includeDefaultBindings, SaveState saveState) {
|
private void writeNonWrappedOptions(boolean includeDefaultBindings,
|
||||||
|
AttributedSaveState saveState) {
|
||||||
for (String optionName : valueMap.keySet()) {
|
for (String optionName : valueMap.keySet()) {
|
||||||
Option optionValue = valueMap.get(optionName);
|
Option option = valueMap.get(optionName);
|
||||||
if (includeDefaultBindings || !optionValue.isDefault()) {
|
if (includeDefaultBindings || !option.isDefault()) {
|
||||||
Object value = optionValue.getValue(null);
|
writeNonWrappedOption(saveState, optionName, option);
|
||||||
if (isSupportedBySaveState(value)) {
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeNonWrappedOption(AttributedSaveState saveState, String optionName,
|
||||||
|
Option option) {
|
||||||
|
Object value = option.getValue(null);
|
||||||
|
if (!isSupportedBySaveState(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
saveState.putObject(optionName, value);
|
saveState.putObject(optionName, value);
|
||||||
}
|
|
||||||
}
|
LocalDate date = option.getLastRegisteredDate();
|
||||||
}
|
String dateString = date.format(LAST_REGISTERED_DATE_FORMATTER);
|
||||||
|
Map<String, String> attrs = Map.of(LAST_REGISTERED_DATE_ATTIBUTE, dateString);
|
||||||
|
saveState.addAttributes(optionName, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeWrappedOptions(boolean includeDefaultBindings, Element root) {
|
private void writeWrappedOptions(boolean includeDefaultBindings, Element root) {
|
||||||
|
@ -195,34 +236,45 @@ public class ToolOptions extends AbstractOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeDefaultBindings || !option.isDefault()) {
|
if (includeDefaultBindings || !option.isDefault()) {
|
||||||
|
writeWrappedOption(root, optionName, option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeWrappedOption(Element root, String optionName, Option option) {
|
||||||
Object value = option.getCurrentValue();
|
Object value = option.getCurrentValue();
|
||||||
if (isSupportedBySaveState(value)) {
|
if (isSupportedBySaveState(value)) {
|
||||||
continue; // handled above
|
return; // handled above
|
||||||
}
|
}
|
||||||
|
|
||||||
WrappedOption wrappedOption = wrapOption(option);
|
WrappedOption wrappedOption = wrapOption(option);
|
||||||
if (wrappedOption == null) {
|
if (wrappedOption == null) {
|
||||||
continue; // cannot write an option without a value to determine its type
|
return; // cannot write an option without a value to determine its type
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveState ss = new SaveState(WRAPPED_OPTION_NAME);
|
SaveState ss = new SaveState(WRAPPED_OPTION_NAME);
|
||||||
Element elem = null;
|
Element element = null;
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
// Handle the null case ourselves, not using the wrapped option (and when
|
// Handle the null case ourselves, not using the wrapped option (and when
|
||||||
// reading from xml) so the logic does not need to be in each wrapped option
|
// reading from xml) so the logic does not need to be in each wrapped option
|
||||||
elem = ss.saveToXml();
|
element = ss.saveToXml();
|
||||||
elem.addContent(new Element(CLEARED_VALUE_ELEMENT_NAME));
|
element.addContent(new Element(CLEARED_VALUE_ELEMENT_NAME));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
wrappedOption.writeState(ss);
|
wrappedOption.writeState(ss);
|
||||||
elem = ss.saveToXml();
|
element = ss.saveToXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.setAttribute(NAME_ATTRIBUTE, optionName);
|
element.setAttribute(NAME_ATTRIBUTE, optionName);
|
||||||
elem.setAttribute(CLASS_ATTRIBUTE, wrappedOption.getClass().getName());
|
|
||||||
root.addContent(elem);
|
String className = wrappedOption.getClass().getName();
|
||||||
}
|
element.setAttribute(CLASS_ATTRIBUTE, className);
|
||||||
}
|
|
||||||
|
LocalDate date = option.getLastRegisteredDate();
|
||||||
|
String dateString = date.format(LAST_REGISTERED_DATE_FORMATTER);
|
||||||
|
element.setAttribute(LAST_REGISTERED_DATE_ATTIBUTE, dateString);
|
||||||
|
|
||||||
|
root.addContent(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSupportedBySaveState(Object obj) {
|
private boolean isSupportedBySaveState(Object obj) {
|
||||||
|
@ -310,7 +362,7 @@ public class ToolOptions extends AbstractOptions {
|
||||||
List<String> optionNames = new ArrayList<>(valueMap.keySet());
|
List<String> optionNames = new ArrayList<>(valueMap.keySet());
|
||||||
for (String optionName : optionNames) {
|
for (String optionName : optionNames) {
|
||||||
Option optionState = valueMap.get(optionName);
|
Option optionState = valueMap.get(optionName);
|
||||||
if (!optionState.isRegistered()) {
|
if (optionState.hasExpired()) {
|
||||||
removeOption(optionName);
|
removeOption(optionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,10 +429,14 @@ public class ToolOptions extends AbstractOptions {
|
||||||
Set<String> keySet = valueMap.keySet();
|
Set<String> keySet = valueMap.keySet();
|
||||||
for (String propertyName : keySet) {
|
for (String propertyName : keySet) {
|
||||||
Option optionState = valueMap.get(propertyName);
|
Option optionState = valueMap.get(propertyName);
|
||||||
if (optionState.isRegistered()) {
|
if (optionState.isRegistered() || optionState.wasRegisteredInPreviousSession()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Msg.warn(this, "Unregistered property \"" + propertyName + "\" in Options \"" + name +
|
|
||||||
|
// getting here means that this option was used in the current tool session, but was not
|
||||||
|
// registered this session or in a previous session
|
||||||
|
Msg.warn(this,
|
||||||
|
"Unregistered property \"" + propertyName + "\" in Options \"" + name +
|
||||||
"\"\n " + optionState.getInceptionInformation());
|
"\"\n " + optionState.getInceptionInformation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,30 +16,29 @@
|
||||||
package ghidra.framework.options;
|
package ghidra.framework.options;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper class for an object that represents a property value and is
|
* Wrapper class for an object that represents a property value and is saved as a set of primitives.
|
||||||
* saved as a set of primitives.
|
|
||||||
*/
|
*/
|
||||||
public interface WrappedOption {
|
public interface WrappedOption {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the object that is the property value.
|
* {@return Get the object that is the property value}
|
||||||
*/
|
*/
|
||||||
public abstract Object getObject();
|
public abstract Object getObject();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concrete subclass of WrappedOption should read all of its
|
* Subclasses of WrappedOption should read all state from the given save state object.
|
||||||
* state from the given saveState object.
|
|
||||||
* @param saveState container of state information
|
* @param saveState container of state information
|
||||||
*/
|
*/
|
||||||
public abstract void readState(SaveState saveState);
|
public abstract void readState(SaveState saveState);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concrete subclass of WrappedOption should write all of its
|
* Subclasses of WrappedOption should write all state to the given save state object.
|
||||||
* state to the given saveState object.
|
|
||||||
* @param saveState container of state information
|
* @param saveState container of state information
|
||||||
*/
|
*/
|
||||||
public abstract void writeState(SaveState saveState);
|
public abstract void writeState(SaveState saveState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return the option type}
|
||||||
|
*/
|
||||||
public abstract OptionType getOptionType();
|
public abstract OptionType getOptionType();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue