diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/ProgramUserDataTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/ProgramUserDataTest.java index dc08557b6a..4d96e94d0d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/ProgramUserDataTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/ProgramUserDataTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.*; import java.io.File; import java.util.Random; +import java.util.Set; import org.junit.*; @@ -142,52 +143,42 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest { DomainFile df2; - Program program = - (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); space = program.getAddressFactory().getDefaultAddressSpace(); - try { - assertFalse(program.isChanged()); - assertTrue(program.canSave()); + assertFalse(program.isChanged()); + assertTrue(program.canSave()); - // Modify program content - no user data should be saved - change(program); - assertTrue(program.isChanged()); - program.save("save", TaskMonitor.DUMMY); - assertFalse(program.isChanged()); - assertFalse("User data directory should be empty", userDataSubDir.isDirectory()); + // Modify program content - no user data should be saved + change(program); + assertTrue(program.isChanged()); + program.save("save", TaskMonitor.DUMMY); + assertFalse(program.isChanged()); + assertFalse("User data directory should be empty", userDataSubDir.isDirectory()); - // Modify user data content - ProgramUserData userData = program.getProgramUserData(); - change(userData, "STRING", space.getAddress(0), "Str0"); - change(userData, "STRING", space.getAddress(10), "Str10"); - assertFalse(program.isChanged()); + // Modify user data content + ProgramUserData userData = program.getProgramUserData(); + change(userData, "STRING", space.getAddress(0), "Str0"); + change(userData, "STRING", space.getAddress(10), "Str10"); + assertFalse(program.isChanged()); - String newName = df.getName() + ".1"; - df2 = df.getParent().createFile(newName, program, TaskMonitor.DUMMY); + String newName = df.getName() + ".1"; + df2 = df.getParent().createFile(newName, program, TaskMonitor.DUMMY); - } - finally { - program.release(this); - } + program.release(this); - program = - (Program) df2.getDomainObject(this, false, false, TaskMonitor.DUMMY); - try { - assertFalse(program.isChanged()); + program = (Program) df2.getDomainObject(this, false, false, TaskMonitor.DUMMY); + assertFalse(program.isChanged()); - // Verify user data content - ProgramUserData userData = program.getProgramUserData(); - StringPropertyMap map = - userData.getStringProperty(testName.getMethodName(), "STRING", false); - assertEquals("Str0", map.getString(space.getAddress(0))); - assertEquals("Str10", map.getString(space.getAddress(10))); - assertNull(map.getString(space.getAddress(20))); - assertFalse(program.isChanged()); - } - finally { - program.release(this); - } + // Verify user data content + userData = program.getProgramUserData(); + StringPropertyMap map = + userData.getStringProperty(testName.getMethodName(), "STRING", false); + assertEquals("Str0", map.getString(space.getAddress(0))); + assertEquals("Str10", map.getString(space.getAddress(10))); + assertNull(map.getString(space.getAddress(20))); + assertFalse(program.isChanged()); + program.release(this); } @Test @@ -204,77 +195,63 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest { File dbDir = new File(dataDir, "00/~00000000.db"); int ver; - Program program = - (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); space = program.getAddressFactory().getDefaultAddressSpace(); - try { - assertFalse(program.isChanged()); - assertTrue(program.canSave()); + assertFalse(program.isChanged()); + assertTrue(program.canSave()); - // Modify program content - no user data should be saved - change(program); - assertTrue(program.isChanged()); - program.save("save", TaskMonitor.DUMMY); - assertFalse(program.isChanged()); - assertFalse("User data directory should be empty", userDataSubDir.isDirectory()); + // Modify program content - no user data should be saved + change(program); + assertTrue(program.isChanged()); + program.save("save", TaskMonitor.DUMMY); + assertFalse(program.isChanged()); + assertFalse("User data directory should be empty", userDataSubDir.isDirectory()); - ver = getLatestDbVersion(dbDir); + ver = getLatestDbVersion(dbDir); - // Modify user data content - ProgramUserData userData = program.getProgramUserData(); - change(userData, "STRING", space.getAddress(0), "Str0"); - change(userData, "STRING", space.getAddress(10), "Str10"); - assertFalse(program.isChanged()); + // Modify user data content + ProgramUserData userData = program.getProgramUserData(); + change(userData, "STRING", space.getAddress(0), "Str0"); + change(userData, "STRING", space.getAddress(10), "Str10"); + assertFalse(program.isChanged()); - } - finally { - program.release(this); - } + program.release(this); assertEquals("User data files missing", 2, userDataSubDir.list().length); assertEquals("Program database should not have been updated", ver, getLatestDbVersion(dbDir)); - program = - (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); - try { - assertFalse(program.isChanged()); + program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + assertFalse(program.isChanged()); - // Verify user data content - ProgramUserData userData = program.getProgramUserData(); - StringPropertyMap map = - userData.getStringProperty(testName.getMethodName(), "STRING", false); - assertEquals("Str0", map.getString(space.getAddress(0))); - assertEquals("Str10", map.getString(space.getAddress(10))); - assertNull(map.getString(space.getAddress(20))); - assertFalse(program.isChanged()); + // Verify user data content + userData = program.getProgramUserData(); + StringPropertyMap map = + userData.getStringProperty(testName.getMethodName(), "STRING", false); + assertEquals("Str0", map.getString(space.getAddress(0))); + assertEquals("Str10", map.getString(space.getAddress(10))); + assertNull(map.getString(space.getAddress(20))); + assertFalse(program.isChanged()); - // Modify user data content - change(userData, "STRING", space.getAddress(10), "Str10a"); - change(userData, "STRING", space.getAddress(20), "Str20a"); - assertFalse(program.isChanged()); - } - finally { - program.release(this); - } + // Modify user data content + change(userData, "STRING", space.getAddress(10), "Str10a"); + change(userData, "STRING", space.getAddress(20), "Str20a"); + assertFalse(program.isChanged()); - program = - (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); - try { - assertFalse(program.isChanged()); + program.release(this); - // Verify user data content - ProgramUserData userData = program.getProgramUserData(); - StringPropertyMap map = - userData.getStringProperty(testName.getMethodName(), "STRING", false); - assertEquals("Str0", map.getString(space.getAddress(0))); - assertEquals("Str10a", map.getString(space.getAddress(10))); - assertEquals("Str20a", map.getString(space.getAddress(20))); - assertFalse(program.isChanged()); - } - finally { - program.release(this); - } + program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + assertFalse(program.isChanged()); + + // Verify user data content + userData = program.getProgramUserData(); + map = userData.getStringProperty(testName.getMethodName(), "STRING", false); + assertEquals("Str0", map.getString(space.getAddress(0))); + assertEquals("Str10a", map.getString(space.getAddress(10))); + assertEquals("Str20a", map.getString(space.getAddress(20))); + assertFalse(program.isChanged()); + + program.release(this); assertEquals("User data files missing", 2, userDataSubDir.list().length); df.delete(); @@ -288,18 +265,14 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest { // TODO: Multi-user repository connect case not tested - Program program = - (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); space = program.getAddressFactory().getDefaultAddressSpace(); - try { - // Create user data content - ProgramUserData userData = program.getProgramUserData(); - change(userData, "STRING", space.getAddress(0), "Str0"); - change(userData, "STRING", space.getAddress(10), "Str10"); - } - finally { - program.release(this); - } + // Create user data content + ProgramUserData userData = program.getProgramUserData(); + change(userData, "STRING", space.getAddress(0), "Str0"); + change(userData, "STRING", space.getAddress(10), "Str10"); + + program.release(this); // Close and re-open (domain file remains intact) project.close(); @@ -308,23 +281,19 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest { df = project.getProjectData().getFile("/test"); - program = - (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); - try { - assertFalse(program.isChanged()); + program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + assertFalse(program.isChanged()); - // Verify user data content - ProgramUserData userData = program.getProgramUserData(); - StringPropertyMap map = - userData.getStringProperty(testName.getMethodName(), "STRING", false); - assertEquals("Str0", map.getString(space.getAddress(0))); - assertEquals("Str10", map.getString(space.getAddress(10))); - assertNull(map.getString(space.getAddress(20))); - assertFalse(program.isChanged()); - } - finally { - program.release(this); - } + // Verify user data content + userData = program.getProgramUserData(); + StringPropertyMap map = + userData.getStringProperty(testName.getMethodName(), "STRING", false); + assertEquals("Str0", map.getString(space.getAddress(0))); + assertEquals("Str10", map.getString(space.getAddress(10))); + assertNull(map.getString(space.getAddress(20))); + assertFalse(program.isChanged()); + + program.release(this); // Close and re-open (domain file removed) project.close(); @@ -351,4 +320,70 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest { } } + @Test + public void testSetGetStringProperty() throws Exception { + Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + + ProgramUserData userData = program.getProgramUserData(); + userData.setStringProperty("Foo", "Bar"); + assertEquals("Bar", userData.getStringProperty("Foo", null)); + program.release(this); + + program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + userData = program.getProgramUserData(); + assertEquals("Bar", userData.getStringProperty("Foo", null)); + program.release(this); + } + + @Test + public void testGetStringPropertyNames() throws Exception { + Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + + ProgramUserData userData = program.getProgramUserData(); + userData.setStringProperty("Alpha", "alpha"); + userData.setStringProperty("Beta", "beta"); + Set stringPropertyNames = userData.getStringPropertyNames(); + assertEquals(2, stringPropertyNames.size()); + assertTrue(stringPropertyNames.contains("Alpha")); + assertTrue(stringPropertyNames.contains("Beta")); + + program.release(this); + } + + @Test + public void testGetStringPropertyForNondefinedProperty() throws Exception { + Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + + ProgramUserData userData = program.getProgramUserData(); + assertEquals("xxx", userData.getStringProperty("ABC", "xxx")); + assertEquals("zzz", userData.getStringProperty("ABC", "zzz")); + + program.release(this); + } + + @Test + public void testRemoveStringProperty() throws Exception { + Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + + ProgramUserData userData = program.getProgramUserData(); + userData.setStringProperty("foo", "bar"); + program.release(this); + + // reload and make sure the property is there + + program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + userData = program.getProgramUserData(); + assertEquals("bar", userData.getStringProperty("foo", null)); + assertEquals("bar", userData.removeStringProperty("foo")); + program.release(this); + + // reload and make sure the property is gone + + program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); + userData = program.getProgramUserData(); + assertNull(userData.getStringProperty("foo", null)); + + program.release(this); + + } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java index 35e48094b4..29640be646 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java @@ -602,6 +602,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter DomainObjectAdapterDB userData = getUserData(); if (userData != null && userData.isChanged() && (getDomainFile() instanceof GhidraFile)) { try { + userData.prepareToSave(); userData.save(null, TaskMonitorAdapter.DUMMY_MONITOR); } catch (CancelledException e) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramUserDataDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramUserDataDB.java index 004425c9dd..77a891dfd6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramUserDataDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramUserDataDB.java @@ -242,8 +242,9 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData Language newLanguage = language; - Language oldLanguage = OldLanguageFactory.getOldLanguageFactory().getOldLanguage( - languageID, languageVersion); + Language oldLanguage = OldLanguageFactory.getOldLanguageFactory() + .getOldLanguage( + languageID, languageVersion); if (oldLanguage == null) { // Assume minor version behavior - old language does not exist for current major version Msg.error(this, "Old language specification not found: " + languageID + @@ -253,8 +254,9 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData // Ensure that we can upgrade the language languageUpgradeTranslator = - LanguageTranslatorFactory.getLanguageTranslatorFactory().getLanguageTranslator( - oldLanguage, newLanguage); + LanguageTranslatorFactory.getLanguageTranslatorFactory() + .getLanguageTranslator( + oldLanguage, newLanguage); if (languageUpgradeTranslator == null) { throw new LanguageNotFoundException(language.getLanguageID(), "(Ver " + languageVersion + ".x" + " -> " + newLanguage.getVersion() + "." + @@ -285,8 +287,9 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData throws LanguageNotFoundException { languageUpgradeTranslator = - LanguageTranslatorFactory.getLanguageTranslatorFactory().getLanguageTranslator( - languageID, languageVersion); + LanguageTranslatorFactory.getLanguageTranslatorFactory() + .getLanguageTranslator( + languageID, languageVersion); if (languageUpgradeTranslator == null) { throw e; } @@ -372,6 +375,7 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) { requiresUpgrade = true; } + loadMetadata(); return requiresUpgrade ? new VersionException(true) : null; } @@ -686,4 +690,27 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData // fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_SAVED)); } + @Override + public void setStringProperty(String propertyName, String value) { + metadata.put(propertyName, value); + changed = true; + } + + @Override + public String getStringProperty(String propertyName, String defaultValue) { + String value = metadata.get(propertyName); + return value == null ? defaultValue : value; + } + + @Override + public Set getStringPropertyNames() { + return metadata.keySet(); + } + + @Override + public String removeStringProperty(String propertyName) { + changed = true; + return metadata.remove(propertyName); + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramUserData.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramUserData.java index b545f6562f..cf563c4dec 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramUserData.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramUserData.java @@ -16,6 +16,7 @@ package ghidra.program.model.listing; import java.util.List; +import java.util.Set; import ghidra.framework.model.UserData; import ghidra.framework.options.Options; @@ -33,16 +34,16 @@ public interface ProgramUserData extends UserData { /** * End a previously started transaction - * @param transactionID + * @param transactionID the id of the transaction to close */ public void endTransaction(int transactionID); /** * Get a address-based String property map * @param owner name of property owner (e.g., plugin name) - * @param propertyName + * @param propertyName the name of property map * @param create creates the property map if it does not exist - * @return property map + * @return the property map for the given name * @throws PropertyTypeMismatchException if a conflicting map definition was found */ public StringPropertyMap getStringProperty(String owner, String propertyName, boolean create) @@ -51,7 +52,7 @@ public interface ProgramUserData extends UserData { /** * Get a address-based Long property map * @param owner name of property owner (e.g., plugin name) - * @param propertyName + * @param propertyName the name of property map * @param create creates the property map if it does not exist * @return property map * @throws PropertyTypeMismatchException if a conflicting map definition was found @@ -62,7 +63,7 @@ public interface ProgramUserData extends UserData { /** * Get a address-based Integer property map * @param owner name of property owner (e.g., plugin name) - * @param propertyName + * @param propertyName the name of property map * @param create creates the property map if it does not exist * @return property map * @throws PropertyTypeMismatchException if a conflicting map definition was found @@ -73,7 +74,7 @@ public interface ProgramUserData extends UserData { /** * Get a address-based Boolean property map * @param owner name of property owner (e.g., plugin name) - * @param propertyName + * @param propertyName the name of property map * @param create creates the property map if it does not exist * @return property map * @throws PropertyTypeMismatchException if a conflicting map definition was found @@ -84,7 +85,8 @@ public interface ProgramUserData extends UserData { /** * Get a address-based Saveable-object property map * @param owner name of property owner (e.g., plugin name) - * @param propertyName + * @param propertyName the name of property map + * @param saveableObjectClass the class type for the object property map * @param create creates the property map if it does not exist * @return property map * @throws PropertyTypeMismatchException if a conflicting map definition was found @@ -101,19 +103,50 @@ public interface ProgramUserData extends UserData { /** * Returns list of all property owners for which property maps have been defined. + * @return list of all property owners for which property maps have been defined. */ public List getPropertyOwners(); /** - * Returns all properties lists contained by this domain object. + * Returns all names of all the Options objects store in the user data * - * @return all property lists contained by this domain object. + * @return all names of all the Options objects store in the user data */ public List getOptionsNames(); /** - * Get the property list for the given name. - * @param propertyListName name of property list + * Get the Options for the given optionsName + * @param optionsName the name of the options options to retrieve + * @return The options for the given name */ - public Options getOptions(String propertyListName); + public Options getOptions(String optionsName); + + /** + * Sets the given String property + * @param propertyName the name of the property + * @param value the value of the property + */ + public void setStringProperty(String propertyName, String value); + + /** + * Gets the value for the given property name + * @param propertyName the name of the string property to retrieve + * @param defaultValue the value to return if there is no saved value for the given name + * @return the value for the given property name + */ + public String getStringProperty(String propertyName, String defaultValue); + + /** + * Removes the String property with the given name; + * @param propertyName the name of the property to remove; + * @return returns the value of the property that was removed or null if the property doesn't + * exist + */ + public String removeStringProperty(String propertyName); + + /** + * Returns a set of all String properties that have been set on this ProgramUserData object + * @return a set of all String properties that have been set on this ProgramUserData object + */ + public Set getStringPropertyNames(); }