diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java index 9796355d9f..d20d8f5b1a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java @@ -23,7 +23,7 @@ import org.jdom.Element; import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.TraceRecorder; import ghidra.dbg.target.TargetObject; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.*; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginTool; @@ -664,7 +664,7 @@ public class DebuggerCoordinates { if (projData == null) { try { // FIXME! orphaned instance - transient in nature - projData = new ProjectFileManager(projLoc, false, false); + projData = new DefaultProjectData(projLoc, false, false); } catch (NotOwnerException e) { Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed", diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java index 774f0dda2c..15af719001 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectTreeModel.java @@ -25,6 +25,7 @@ import docking.widgets.tree.GTreeNode; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.dbg.target.*; import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator; +import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObjectClosedListener; import ghidra.trace.model.*; import ghidra.trace.model.Trace.TraceObjectChangeType; @@ -45,7 +46,7 @@ public class ObjectTreeModel implements DisplaysModified { } @Override - public void domainObjectClosed() { + public void domainObjectClosed(DomainObject dobj) { setTrace(null); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java index b7d0302d1e..fcb9fd0314 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/modules/ProgramModuleIndexer.java @@ -94,7 +94,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter { } @Override - public void domainObjectClosed() { + public void domainObjectClosed(DomainObject dobj) { + // assume dobj == program dispose(); } @@ -353,9 +354,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter { * trace, or bogus external libraries in a mapped program, scoring libraries before module * names should not cause problems. */ - Comparator comparator = byIsLibrary - .thenComparing(byNameSource) - .thenComparing(byFolderUses); + Comparator comparator = + byIsLibrary.thenComparing(byNameSource).thenComparing(byFolderUses); return projectData.getFileByID(entries.stream().max(comparator).get().dfID); } diff --git a/Ghidra/Features/Base/developer_scripts/CleanupMergeDatabasesScript.java b/Ghidra/Features/Base/developer_scripts/CleanupMergeDatabasesScript.java index e4a9111353..335e377ef6 100644 --- a/Ghidra/Features/Base/developer_scripts/CleanupMergeDatabasesScript.java +++ b/Ghidra/Features/Base/developer_scripts/CleanupMergeDatabasesScript.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import java.io.IOException; +import java.lang.reflect.Method; + import ghidra.app.script.GhidraScript; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.Project; import ghidra.framework.store.FileSystem; import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFolderItem; -import java.io.IOException; -import java.lang.reflect.Method; - public class CleanupMergeDatabasesScript extends GhidraScript { @Override @@ -31,8 +30,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript { Project project = state.getProject(); - ProjectFileManager fileMgr = (ProjectFileManager) project.getProjectData(); - LocalFileSystem fs = (LocalFileSystem) fileMgr.getPrivateFileSystem(); + DefaultProjectData projectData = (DefaultProjectData) project.getProjectData(); + LocalFileSystem fs = (LocalFileSystem) projectData.getPrivateFileSystem(); int cnt = cleanupFolder(fs, "/"); @@ -61,9 +60,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript { } // fs.getItemNames(folderPath, true) - String[] itemNames = - (String[]) invokeInstanceMethod("getItemNames", fs, new Class[] { String.class, - boolean.class }, new Object[] { folderPath, true }); + String[] itemNames = (String[]) invokeInstanceMethod("getItemNames", fs, + new Class[] { String.class, boolean.class }, new Object[] { folderPath, true }); for (String itemName : itemNames) { if (!itemName.startsWith(LocalFileSystem.HIDDEN_ITEM_PREFIX)) { @@ -78,8 +76,9 @@ public class CleanupMergeDatabasesScript extends GhidraScript { else { // make sure we get item out of index //fs.deallocateItemStorage(folderPath, itemName); - invokeInstanceMethod("deallocateItemStorage", fs, new Class[] { String.class, - String.class }, new Object[] { folderPath, itemName }); + invokeInstanceMethod("deallocateItemStorage", fs, + new Class[] { String.class, String.class }, + new Object[] { folderPath, itemName }); } ++cnt; } diff --git a/Ghidra/Features/Base/ghidra_scripts/AskScript.java b/Ghidra/Features/Base/ghidra_scripts/AskScript.java index 6c4d478cd1..debe53c50f 100644 --- a/Ghidra/Features/Base/ghidra_scripts/AskScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/AskScript.java @@ -75,7 +75,16 @@ public class AskScript extends GhidraScript { } Program prog = askProgram("Please choose a program to open."); - println("Program picked: " + prog.getName()); + if (prog != null) { + // NOTE: if prog is not null script must release it when done using. + // This may also be accomplished via an overridden cleanup(boolean) method. + try { + println("Program picked: " + prog.getName()); + } + finally { + prog.release(this); // will remain open in tool if applicable + } + } DomainFile domFile = askDomainFile("Which domain file would you like?"); println("Domain file: " + domFile.getName()); diff --git a/Ghidra/Features/Base/ghidra_scripts/CompareAnalysisScript.java b/Ghidra/Features/Base/ghidra_scripts/CompareAnalysisScript.java index bf0045c2b9..93812a8138 100644 --- a/Ghidra/Features/Base/ghidra_scripts/CompareAnalysisScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/CompareAnalysisScript.java @@ -38,19 +38,24 @@ public class CompareAnalysisScript extends GhidraScript { if (otherProgram == null) { return; } - println("\n\n****** COMPARING FUNCTIONS:\n"); - compareFunctions(otherProgram); - println("\n\n****** COMPARING STRINGS:\n"); - compareStrings(otherProgram); - println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n"); - reportPercentDisassembled(currentProgram); - reportPercentDisassembled(otherProgram); - println("\n\n****** COMPARING SWITCH TABLES:\n"); - compareSwitchTables(otherProgram); - println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n"); - compareNoReturns(otherProgram); - println("\n\n****** COMPARING ERRORS:\n"); - compareErrors(otherProgram); + try { + println("\n\n****** COMPARING FUNCTIONS:\n"); + compareFunctions(otherProgram); + println("\n\n****** COMPARING STRINGS:\n"); + compareStrings(otherProgram); + println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n"); + reportPercentDisassembled(currentProgram); + reportPercentDisassembled(otherProgram); + println("\n\n****** COMPARING SWITCH TABLES:\n"); + compareSwitchTables(otherProgram); + println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n"); + compareNoReturns(otherProgram); + println("\n\n****** COMPARING ERRORS:\n"); + compareErrors(otherProgram); + } + finally { + otherProgram.release(this); + } } void compareFunctions(Program otherProgram) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisStateInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisStateInfo.java index e0dcc46ac6..03676c512d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisStateInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisStateInfo.java @@ -73,7 +73,7 @@ public class AnalysisStateInfo { if (stateMap == null) { stateMap = new HashMap<>(); programStates.put(program, stateMap); - program.addCloseListener(() -> programStates.remove(program)); + program.addCloseListener(doa -> programStates.remove(program)); } stateMap.put(state.getClass(), state); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java index 5be6e12775..4ffd6bc115 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisManager.java @@ -57,7 +57,7 @@ import ghidra.util.task.*; * Provides support for auto analysis tasks. * Manages a pipeline or priority of tasks to run given some event has occurred. */ -public class AutoAnalysisManager implements DomainObjectListener, DomainObjectClosedListener { +public class AutoAnalysisManager implements DomainObjectListener { /** * The name of the shared thread pool that analyzers can uses to do parallel processing. @@ -145,7 +145,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl private AutoAnalysisManager(Program program) { this.program = program; eventQueueID = program.createPrivateEventQueue(this, 500); - program.addCloseListener(this); + program.addCloseListener(dobj -> dispose()); initializeAnalyzers(); } @@ -361,11 +361,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl subType == ChangeManager.FUNCTION_CHANGED_RETURN; } - @Override - public void domainObjectClosed() { - dispose(); - } - @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { if (program == null) { @@ -961,10 +956,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl } PluginTool anyTool = null; - Iterator iterator = toolSet.iterator(); - while (iterator.hasNext()) { - PluginTool tool = iterator.next(); - + for (PluginTool tool : toolSet) { anyTool = tool; JFrame toolFrame = tool.getToolFrame(); if (toolFrame != null && toolFrame.isActive()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java index 15b2943795..9f7be5042c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java @@ -33,7 +33,6 @@ import ghidra.app.nav.Navigatable; import ghidra.app.services.*; import ghidra.app.util.viewer.listingpanel.*; import ghidra.app.util.viewer.util.AddressIndexMap; -import ghidra.framework.model.DomainObjectClosedListener; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; @@ -510,30 +509,19 @@ public class MarkerManager implements MarkerService { private final AddressColorCache colorCache = new AddressColorCache(); private final ColorBlender blender = new ColorBlender(); - private final MarkerSetCache cache; - private final Program program; - private final DomainObjectClosedListener closeListener = this::programClosed; - public MarkerSetCacheEntry(MarkerSetCache cache, Program program) { - this.cache = cache; - this.program = program; /** * Use this close listener approach instead of plugin events, since we don't get a * ProgramClosedPluginEvent when a trace view is closed, but we can listen for its * domain object closing, which works for plain programs, too. */ - program.addCloseListener(closeListener); + program.addCloseListener(dobj -> cache.programClosed(program)); } void clearColors() { colorCache.clear(); } - private void programClosed() { - program.removeCloseListener(closeListener); - cache.programClosed(program); - } - MarkerSetImpl getByName(String name) { for (MarkerSetImpl set : markerSets) { if (name.equals(set.getName())) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index 6865986704..818892110d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -1883,6 +1883,9 @@ public abstract class GhidraScript extends FlatProgramAPI { * @param transformer the function to turn a String into a T * @param key the values used to create a key for lookup in the script properties file * @return null if no value was found in the aforementioned sources + * @throws IllegalArgumentException if the loaded String value cannot be parsed into a + * T or property not defined when in headless + * mode. */ private T loadAskValue(StringTransformer transformer, String key) { T value = loadAskValue(null, transformer, key); @@ -1897,11 +1900,12 @@ public abstract class GhidraScript extends FlatProgramAPI { * @param defaultValue an optional default value that will be used if no suitable * value can be found in script args or a properties file * @param transformer the function to turn a String into a T - * @param key the values used to create a key for lookup in the script properties file + * @param key the value property key used for lookup in the script properties file * @return null if no value was found in the aforementioned sources * * @throws IllegalArgumentException if the loaded String value cannot be parsed into a - * T. + * T or property not defined when in headless + * mode and no defaultValue has been specified. */ private T loadAskValue(T defaultValue, StringTransformer transformer, String key) { @@ -2513,7 +2517,7 @@ public abstract class GhidraScript extends FlatProgramAPI { public Address askAddress(String title, String message) throws CancelledException { return askAddress(title, message, null); } - + /** * Returns an Address, using the String parameters for guidance. The actual behavior of the * method depends on your environment, which can be GUI or headless. @@ -2550,15 +2554,16 @@ public abstract class GhidraScript extends FlatProgramAPI { * @throws IllegalArgumentException if in headless mode, there was a missing or invalid Address * specified in the .properties file */ - public Address askAddress(String title, String message, String defaultValue) throws CancelledException { + public Address askAddress(String title, String message, String defaultValue) + throws CancelledException { String key = join(title, message); - + Address defaultAddr = null; if (defaultValue != null) { defaultAddr = currentProgram.getAddressFactory().getAddress(defaultValue); } - + // if defaultAddr is null then it assumes no default value Address existingValue = loadAskValue(defaultAddr, this::parseAddress, key); if (isRunningHeadless()) { @@ -2683,7 +2688,12 @@ public abstract class GhidraScript extends FlatProgramAPI { * * @param title the title of the pop-up dialog (in GUI mode) or the variable name (in * headless mode) - * @return the user-specified Program + * @return the user-selected Program with this script as the consumer or null if a program was + * not selected. NOTE: It is very important that the program instance returned by this method + * ALWAYS be properly released when no longer needed. The script which invoked this method must be + * specified as the consumer upon release (i.e., {@code program.release(this) } - failure to + * properly release the program may result in improper project disposal. If the program was + * opened by the tool, the tool will be a second consumer responsible for its own release. * @throws VersionException if the Program is out-of-date from the version of GHIDRA * @throws IOException if there is an error accessing the Program's DomainObject * @throws CancelledException if the operation is cancelled @@ -2693,33 +2703,34 @@ public abstract class GhidraScript extends FlatProgramAPI { public Program askProgram(String title) throws VersionException, IOException, CancelledException { - DomainFile existingValue = loadAskValue(this::parseDomainFile, title); - if (isRunningHeadless()) { - return (Program) existingValue.getDomainObject(this, false, false, monitor); + DomainFile choice = loadAskValue(this::parseDomainFile, title); + if (!isRunningHeadless()) { + choice = doAsk(Program.class, title, "", choice, lastValue -> { + + DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN); + dtd.show(); + if (dtd.wasCancelled()) { + throw new CancelledException(); + } + + return dtd.getDomainFile(); + }); } - DomainFile choice = doAsk(Program.class, title, "", existingValue, lastValue -> { - - DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN); - dtd.show(); - if (dtd.wasCancelled()) { - throw new CancelledException(); - } - - return dtd.getDomainFile(); - }); - if (choice == null) { return null; } + Program p = (Program) choice.getDomainObject(this, false, false, monitor); + PluginTool tool = state.getTool(); if (tool == null) { - return (Program) choice.getDomainObject(this, false, false, monitor); + return p; } ProgramManager pm = tool.getService(ProgramManager.class); - return pm.openProgram(choice); + pm.openProgram(p); + return p; } /** @@ -2768,10 +2779,10 @@ public abstract class GhidraScript extends FlatProgramAPI { * * @param title the title of the pop-up dialog (in GUI mode) or the variable name (in headless * mode or when using .properties file) - * @throws IllegalArgumentException if in headless mode, there was a missing or invalid domain - * file specified in the .properties file * @return the user-selected domain file * @throws CancelledException if the operation is cancelled + * @throws IllegalArgumentException if in headless mode, there was a missing or invalid domain + * file specified in the .properties file */ public DomainFile askDomainFile(String title) throws CancelledException { @@ -3015,8 +3026,7 @@ public abstract class GhidraScript extends FlatProgramAPI { throw new ImproperUseException( "The askPassword() method can only be used when running headed Ghidra."); } - PasswordDialog dialog = - new PasswordDialog(title, null, null, prompt, null, null); + PasswordDialog dialog = new PasswordDialog(title, null, null, prompt, null, null); try { state.getTool().showDialog(dialog); if (!dialog.okWasPressed()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index 8149608689..0d4b0b5274 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -878,7 +878,7 @@ public class HeadlessAnalyzer { // Get parent folder to pass to GhidraScript File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI()) - .getParentFile(); + .getParentFile(); currScript = (GhidraScript) c.getConstructor().newInstance(); currScript.setScriptArgs(scriptArgs); @@ -1575,13 +1575,12 @@ public class HeadlessAnalyzer { } else { if (options.readOnly) { - Msg.info(this, "REPORT: Discarded file import due to readOnly option: " + - loaded); + Msg.info(this, + "REPORT: Discarded file import due to readOnly option: " + loaded); } else { - Msg.info(this, - "REPORT: Discarded file import as a result of script " + - "activity or analysis timeout: " + loaded); + Msg.info(this, "REPORT: Discarded file import as a result of script " + + "activity or analysis timeout: " + loaded); } } } @@ -1627,9 +1626,9 @@ public class HeadlessAnalyzer { } } - private LoadResults loadPrograms(File file, String folderPath) throws VersionException, - InvalidNameException, DuplicateNameException, CancelledException, IOException, - LoadException { + private LoadResults loadPrograms(File file, String folderPath) + throws VersionException, InvalidNameException, DuplicateNameException, + CancelledException, IOException, LoadException { MessageLog messageLog = new MessageLog(); if (options.loaderClass == null) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/ProjectFileManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/DefaultProjectDataTest.java similarity index 93% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/ProjectFileManagerTest.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/DefaultProjectDataTest.java index 1cd4a2776a..09e77fbbaa 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/ProjectFileManagerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/DefaultProjectDataTest.java @@ -28,6 +28,7 @@ import org.junit.*; import generic.test.TestUtils; import ghidra.framework.model.*; +import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.store.FileSystem; import ghidra.framework.store.FileSystemEventManager; import ghidra.framework.store.local.LocalFileSystem; @@ -37,13 +38,13 @@ import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.util.task.TaskMonitor; -public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest { +public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest { private File privateProjectDir; private File sharedProjectDir; private FileSystem sharedFS; private LocalFileSystem privateFS; - private ProjectFileManager fileMgr; + private DefaultProjectData projectData; private DomainFolder root; private List events = new ArrayList<>(); @@ -83,9 +84,9 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false, true, false, true); - fileMgr = new ProjectFileManager(privateFS, sharedFS); - fileMgr.addDomainFolderChangeListener(new MyDomainFolderChangeListener()); - root = fileMgr.getRootFolder(); + projectData = new DefaultProjectData(privateFS, sharedFS); + projectData.addDomainFolderChangeListener(new MyDomainFolderChangeListener()); + root = projectData.getRootFolder(); flushFileSystemEventsAndClearTestQueue(); } @@ -97,7 +98,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @After public void tearDown() { - fileMgr.dispose(); + projectData.dispose(); deleteAll(privateProjectDir); deleteAll(sharedProjectDir); } @@ -136,9 +137,15 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest } } + @Test + public void testGetLocalProjectURL() { + assertEquals(GhidraURL.makeURL(projectData.getProjectLocator()), + projectData.getLocalProjectURL()); + } + @Test public void testGetRootFolder() throws Exception { - DomainFolder rootFolder = fileMgr.getRootFolder(); + DomainFolder rootFolder = projectData.getRootFolder(); assertEquals("/", rootFolder.getPathname()); assertEquals(3, rootFolder.getFolders().length); } @@ -146,11 +153,11 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @Test public void testGetFolder() throws Exception { - DomainFolder rootFolder = fileMgr.getRootFolder(); - DomainFolder df1 = fileMgr.getFolder("/"); - DomainFolder df2 = fileMgr.getFolder("/a"); - DomainFolder df3 = fileMgr.getFolder("/a/y"); - DomainFolder df4 = fileMgr.getFolder("/a/x"); + DomainFolder rootFolder = projectData.getRootFolder(); + DomainFolder df1 = projectData.getFolder("/"); + DomainFolder df2 = projectData.getFolder("/a"); + DomainFolder df3 = projectData.getFolder("/a/y"); + DomainFolder df4 = projectData.getFolder("/a/x"); assertNotNull(rootFolder); assertEquals(rootFolder, df1); @@ -178,7 +185,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @Test public void testCreateFile() throws Exception { - DomainFolder folder = fileMgr.getFolder("/a"); + DomainFolder folder = projectData.getFolder("/a"); folder.getFiles(); // visit folder to receive change events from this folder flushFileSystemEventsAndClearTestQueue(); @@ -195,18 +202,18 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest assertEventsSize(2); checkEvent(events.get(1), "File Added", null, null, "/a/file2", null, null); - DomainFile df = fileMgr.getFileByID(fileID1); + DomainFile df = projectData.getFileByID(fileID1); assertNotNull(df); assertEquals("file1", df.getName()); assertTrue(!df.isVersioned()); - df = fileMgr.getFileByID(fileID2); + df = projectData.getFileByID(fileID2); assertNotNull(df2); assertEquals("file2", df.getName()); df1.addToVersionControl("", false, TaskMonitor.DUMMY); - df = fileMgr.getFileByID(fileID1); + df = projectData.getFileByID(fileID1); assertNotNull(df1); assertEquals("file1", df.getName()); assertTrue(df.isVersioned()); @@ -216,7 +223,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @Test public void testFileIndex() throws Exception { - DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", fileMgr); + DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", projectData); assertNotNull(fileIndex); @SuppressWarnings("unchecked") @@ -224,21 +231,21 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest (HashMap) getInstanceField("fileIdToPathIndex", fileIndex); assertNotNull(fileIdToPathIndex); - DomainFolder folder = fileMgr.getFolder("/a"); + DomainFolder folder = projectData.getFolder("/a"); DomainFile df1 = createFile(folder, "file1"); String fileID = df1.getFileID(); - assertEquals(df1, fileMgr.getFileByID(fileID)); + assertEquals(df1, projectData.getFileByID(fileID)); // invalidate folder data to force search - GhidraFolderData rootFolderData = fileMgr.getRootFolderData(); + GhidraFolderData rootFolderData = projectData.getRootFolderData(); rootFolderData.dispose(); assertTrue(fileIdToPathIndex.isEmpty()); // folder invalidation should cause map to clear - assertEquals(df1, fileMgr.getFileByID(fileID)); + assertEquals(df1, projectData.getFileByID(fileID)); assertFalse(fileIdToPathIndex.isEmpty()); // index should become populated } @@ -246,7 +253,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @Test public void testFileIndexUndoCheckout() throws Exception { // TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition - DomainFolder folder = fileMgr.getFolder("/a"); + DomainFolder folder = projectData.getFolder("/a"); DomainFile df1 = createFile(folder, "file1"); String fileID = df1.getFileID(); @@ -265,7 +272,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @Test public void testFileIndexHijack() throws Exception { // TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition - DomainFolder folder = fileMgr.getFolder("/a"); + DomainFolder folder = projectData.getFolder("/a"); folder.getFiles(); // visit folder to enable folder change listener // create shared file /a/file1 and keep checked-out @@ -285,7 +292,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest df1.setName("file2"); - DomainFile df2 = fileMgr.getFile("/a/file2"); + DomainFile df2 = projectData.getFile("/a/file2"); assertTrue(!fileID.equals(df2.getFileID())); @@ -451,7 +458,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @Test public void testFolderRenamedEvent3() throws Exception { - fileMgr.getFolder("/a"); // force folder refresh to reduce event count + projectData.getFolder("/a"); // force folder refresh to reduce event count flushFileSystemEventsAndClearTestQueue(); // exists in localFS so "b" folder should not get created again @@ -495,7 +502,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest @Test public void testRenameFolder6() throws Exception { - DomainFolder aFolder = fileMgr.getFolder("/a"); + DomainFolder aFolder = projectData.getFolder("/a"); assertNotNull(aFolder); aFolder.getFolders(); // visit folder to receive change events for it @@ -601,12 +608,12 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest // versioned folder was moved to /c/a, but private folder /a should still exist - GhidraFolder folder = (GhidraFolder) fileMgr.getFolder("/a"); + GhidraFolder folder = (GhidraFolder) projectData.getFolder("/a"); assertNotNull(folder); assertTrue(folder.privateExists()); assertFalse(folder.sharedExists()); - folder = (GhidraFolder) fileMgr.getFolder("/c/a"); + folder = (GhidraFolder) projectData.getFolder("/c/a"); assertNotNull(folder); assertFalse(folder.privateExists()); assertTrue(folder.sharedExists()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFileTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFileTest.java index 0798cbb592..82362c4269 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFileTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFileTest.java @@ -41,7 +41,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest { private FileSystem sharedFS; private LocalFileSystem privateFS; - private ProjectFileManager pfm; + private DefaultProjectData projectData; private GhidraFolder root; public GhidraFileTest() { @@ -76,8 +76,8 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest { false, false, true); sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false, true, false, true); - pfm = new ProjectFileManager(privateFS, sharedFS); - root = pfm.getRootFolder(); + projectData = new DefaultProjectData(privateFS, sharedFS); + root = projectData.getRootFolder(); } @@ -88,12 +88,12 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest { } @Test - public void testLocalURL() throws IOException { + public void testGetLocalProjectURL() throws IOException { createDB(privateFS, "/a", "file1"); - assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", "xyz"), - pfm.getFile("/a/file1").getLocalProjectURL("xyz")); - assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", null), - pfm.getFile("/a/file1").getLocalProjectURL(null)); + assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", "xyz"), + projectData.getFile("/a/file1").getLocalProjectURL("xyz")); + assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", null), + projectData.getFile("/a/file1").getLocalProjectURL(null)); } @Test @@ -345,7 +345,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest { private void refresh() throws IOException { // refresh everything regardless of visited state - pfm.refresh(true); + projectData.refresh(true); } private void deleteAll(File file) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFolderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFolderTest.java index 0978d9a96f..8c7b80b799 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFolderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/GhidraFolderTest.java @@ -22,6 +22,8 @@ import java.io.IOException; import org.junit.*; +import ghidra.framework.model.ProjectLocator; +import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.store.local.LocalFileSystem; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -31,6 +33,7 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest { private LocalFileSystem sharedFS; private LocalFileSystem privateFS; + private DefaultProjectData projectData; private GhidraFolder root; public GhidraFolderTest() { @@ -68,8 +71,8 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest { false, false, true); sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false, true, false, true); - ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS); - root = projectFileManager.getRootFolder(); + projectData = new DefaultProjectData(privateFS, sharedFS); + root = projectData.getRootFolder(); } private void deleteTestFiles() { @@ -82,6 +85,15 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest { deleteTestFiles(); } + @Test + public void testGetLocalProjectURL() { + ProjectLocator projectLocator = projectData.getProjectLocator(); + assertEquals(GhidraURL.makeURL(projectLocator, "/a/y", null), + projectData.getFolder("/a/y").getLocalProjectURL()); + assertEquals(GhidraURL.makeURL(projectLocator, "/a/x", null), + projectData.getFolder("/a/x").getLocalProjectURL()); + } + @Test public void testGetFolderNames() throws Exception { GhidraFolder[] folders = root.getFolders(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/IndexedFileSystemFolderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/IndexedFileSystemFolderTest.java index b84459b861..6079ba26f3 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/IndexedFileSystemFolderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/data/IndexedFileSystemFolderTest.java @@ -15,24 +15,23 @@ */ package ghidra.framework.data; -import ghidra.framework.model.DomainFolder; -import ghidra.framework.model.Project; -import ghidra.framework.store.local.LocalFileSystem; -import ghidra.test.AbstractGhidraHeadedIntegrationTest; -import ghidra.util.InvalidNameException; - import java.io.File; import java.io.IOException; import org.junit.*; +import ghidra.framework.model.DomainFolder; +import ghidra.framework.store.local.LocalFileSystem; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.util.InvalidNameException; + public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegrationTest { private File testRootDir; private File privateProjectDir; private File sharedProjectDir; private DomainFolder root; - private Project project; + private LocalFileSystem sharedFS; private LocalFileSystem privateFS; @@ -52,8 +51,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration true, false, false); sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), true, true, false, false); - ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS); - root = projectFileManager.getRootFolder(); + DefaultProjectData projectData = new DefaultProjectData(privateFS, sharedFS); + root = projectData.getRootFolder(); } @After @@ -68,8 +67,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration private void deleteAll(File file) { if (file.isDirectory()) { File[] files = file.listFiles(); - for (int i = 0; i < files.length; i++) { - deleteAll(files[i]); + for (File file2 : files) { + deleteAll(file2); } } file.delete(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/FrontEndPluginActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/FrontEndPluginActionsTest.java index 1f516bbb64..5b32a8c9e5 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/FrontEndPluginActionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/FrontEndPluginActionsTest.java @@ -903,8 +903,8 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe // //@formatter:off - Object projectFileManager = getInstanceField("fileManager", df); - invokeInstanceMethod("setDomainObject", projectFileManager, + Object projectData = getInstanceField("projectData", df); + invokeInstanceMethod("setDomainObject", projectData, new Class[] { String.class, DomainObjectAdapter.class }, new Object[] { path, program } ); @@ -962,8 +962,7 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe } } - return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree, - true); + return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree, true); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/AddViewToProjectTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/AddViewToProjectTest.java index 0b4cdd321a..3cea12ad16 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/AddViewToProjectTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/project/AddViewToProjectTest.java @@ -15,16 +15,18 @@ */ package ghidra.framework.project; +import static org.junit.Assert.*; + import java.net.URL; import org.junit.*; -import generic.test.AbstractGenericTest; -import ghidra.framework.model.Project; -import ghidra.framework.model.ProjectLocator; +import ghidra.framework.data.DefaultProjectData; +import ghidra.framework.model.*; import ghidra.framework.protocol.ghidra.GhidraURL; -import ghidra.test.AbstractGhidraHeadlessIntegrationTest; -import ghidra.test.ProjectTestUtils; +import ghidra.test.*; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; /** * Test class for adding and a view to a project, and removing @@ -32,7 +34,7 @@ import ghidra.test.ProjectTestUtils; */ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest { - private final static String DIRECTORY_NAME = AbstractGenericTest.getTestDirectoryPath(); + private final static String DIRECTORY_NAME = getTestDirectoryPath(); private final static String PROJECT_NAME1 = "TestAddViewToProject"; private final static String PROJECT_VIEW1 = "TestView1"; private final static String PROJECT_VIEW2 = "TestView2"; @@ -52,24 +54,9 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest ProjectTestUtils.deleteProject(DIRECTORY_NAME, PROJECT_VIEW2); } - /** - * Do the test. - * @param args same args that are passed to RegressionTester.main() - */ @Test public void testAddToView() throws Exception { -// String filename = System.getProperty("user.dir") + -// File.separator + "testGhidraPreferences"; -// -// try { -// Preferences.load(filename); -// -// } catch (IOException e) { -// } -// -// Preferences.setFilename(filename); - // make sure we have projects to use as the project view... ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1).close(); ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW2).close(); @@ -87,12 +74,12 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest // validate the view was added to project ProjectLocator[] projViews = project.getProjectViews(); for (ProjectLocator projView : projViews) { - System.out.println("added view: " + projView); + Msg.debug(this, "** added view: " + projView); } // remove the view... project.removeProjectView(view); - System.out.println("removed view: " + view); + Msg.debug(this, "** removed view: " + view); projViews = project.getProjectViews(); for (ProjectLocator projView : projViews) { @@ -106,4 +93,59 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest } } + @Test + public void testCloseViewWithOpenProgram() throws Exception { + + DomainObject dobj = null; + + // make sure we have projects to use as the project view... + Project project = ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1); + try { + ToyProgramBuilder builder = new ToyProgramBuilder("Test", true); + DomainFolder rootFolder = project.getProjectData().getRootFolder(); + rootFolder.createFile("Test", builder.getProgram(), TaskMonitor.DUMMY); + builder.dispose(); + project.close(); + + // get project (create it if it doesn't exist...) + project = ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_NAME1); + + URL view = GhidraURL.makeURL(DIRECTORY_NAME, PROJECT_VIEW1); + DefaultProjectData projectData = + (DefaultProjectData) project.addProjectView(view, true); + Msg.debug(this, "** added view: " + view); + assertNotNull(projectData); + + DomainFile f = projectData.getFile("/Test"); + assertNotNull(f); + + // Open file and hold onto + dobj = f.getDomainObject(this, true, false, TaskMonitor.DUMMY); + Msg.debug(this, "** opened program: " + f); + + assertFalse(projectData.isClosed()); + assertFalse(projectData.isDisposed()); + + // remove the view while program open... + project.removeProjectView(view); + Msg.debug(this, "** removed view: " + view); + + assertTrue(projectData.isClosed()); + assertFalse(projectData.isDisposed()); + + Msg.debug(this, "** releasing program: " + f); + dobj.release(this); + dobj = null; + + assertTrue(projectData.isClosed()); + assertTrue(projectData.isDisposed()); + } + finally { + if (dobj != null) { + dobj.release(this); + } + project.close(); + } + } + } 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 4d96e94d0d..4dc2973ed7 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 @@ -24,7 +24,7 @@ import java.util.Set; import org.junit.*; import generic.test.AbstractGTest; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.*; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; @@ -57,8 +57,8 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest { projectLocator = new ProjectLocator(TEMP, "Test"); project = TestProjectManager.get().createProject(projectLocator, null, true); dataDir = - new File(projectLocator.getProjectDir(), ProjectFileManager.INDEXED_DATA_FOLDER_NAME); - userDir = new File(projectLocator.getProjectDir(), ProjectFileManager.USER_FOLDER_NAME); + new File(projectLocator.getProjectDir(), DefaultProjectData.INDEXED_DATA_FOLDER_NAME); + userDir = new File(projectLocator.getProjectDir(), DefaultProjectData.USER_FOLDER_NAME); ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY); df = project.getProjectData() diff --git a/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java b/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java index 075b87a862..3de5d6e918 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/base/project/FakeSharedProject.java @@ -81,8 +81,8 @@ public class FakeSharedProject { // Note: this how we share multiple projects void setVersionedFileSystem(LocalFileSystem fs) { - ProjectFileManager fm = getProjectFileManager(); - invokeInstanceMethod("setVersionedFileSystem", fm, argTypes(FileSystem.class), args(fs)); + DefaultProjectData pd = getProjectData(); + invokeInstanceMethod("setVersionedFileSystem", pd, argTypes(FileSystem.class), args(fs)); } /** @@ -94,12 +94,12 @@ public class FakeSharedProject { } /** - * Gets the project file manager + * Gets the project data instance * - * @return the project file manager + * @return the project data instance */ - public ProjectFileManager getProjectFileManager() { - return (ProjectFileManager) gProject.getProjectData(); + public DefaultProjectData getProjectData() { + return (DefaultProjectData) gProject.getProjectData(); } /** @@ -108,8 +108,8 @@ public class FakeSharedProject { * @return the root folder of this project */ public RootGhidraFolder getRootFolder() { - ProjectFileManager pfm = getProjectFileManager(); - return (RootGhidraFolder) pfm.getRootFolder(); + DefaultProjectData pd = getProjectData(); + return (RootGhidraFolder) pd.getRootFolder(); } /** @@ -181,6 +181,7 @@ public class FakeSharedProject { *
  • calling {@link #addDomainFile(String)}
  • *
  • Adding a versioned file to another project that shares the same repo with this project
  • * + * @param parentPath the parent folder path * @param filename the filename * @return the file */ @@ -194,8 +195,7 @@ public class FakeSharedProject { * Creates a folder by the given name in the given parent folder, creating the parent * folder if needed * - * @param parentPath the parent folder path - * @param name the name of the folder to create + * @param path the full path of the folder to create * @return the created folder * @throws Exception if there are any exceptions creating the folder */ @@ -367,7 +367,7 @@ public class FakeSharedProject { * @see FakeRepository#dispose() */ public void dispose() { - ProjectLocator projectLocator = getProjectFileManager().getProjectLocator(); + ProjectLocator projectLocator = getProjectData().getProjectLocator(); programManager.disposeOpenPrograms(); gProject.close(); FileUtilities.deleteDir(projectLocator.getProjectDir()); @@ -388,7 +388,7 @@ public class FakeSharedProject { } ProjectLocator pl = df.getProjectLocator(); - ProjectLocator mypl = getProjectFileManager().getProjectLocator(); + ProjectLocator mypl = getProjectData().getProjectLocator(); if (!pl.equals(mypl)) { throw new IllegalArgumentException("Domain file '" + df + "' is not in this project: " + mypl.getName() + "\nYou must call addDomainFile(filename)."); @@ -397,9 +397,8 @@ public class FakeSharedProject { private void waitForFileSystemEvents() { LocalFileSystem versionedFileSystem = getVersionedFileSystem(); - FileSystemEventManager eventManager = - (FileSystemEventManager) TestUtils.getInstanceField("eventManager", - versionedFileSystem); + FileSystemEventManager eventManager = (FileSystemEventManager) TestUtils + .getInstanceField("eventManager", versionedFileSystem); eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS); } @@ -444,16 +443,16 @@ public class FakeSharedProject { } LocalFileSystem getVersionedFileSystem() { - ProjectFileManager fileManager = getProjectFileManager(); + DefaultProjectData projectData = getProjectData(); LocalFileSystem fs = - (LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", fileManager); + (LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", projectData); return fs; } void refresh() { - ProjectFileManager fileManager = getProjectFileManager(); + DefaultProjectData projectData = getProjectData(); try { - fileManager.refresh(true); + projectData.refresh(true); } catch (IOException e) { // shouldn't happen diff --git a/Ghidra/Features/FileFormats/ghidra_scripts/MergeTwoProgramsScript.java b/Ghidra/Features/FileFormats/ghidra_scripts/MergeTwoProgramsScript.java index 6e330f924f..38031b800b 100644 --- a/Ghidra/Features/FileFormats/ghidra_scripts/MergeTwoProgramsScript.java +++ b/Ghidra/Features/FileFormats/ghidra_scripts/MergeTwoProgramsScript.java @@ -33,215 +33,219 @@ import ghidra.program.model.listing.*; import ghidra.program.model.mem.*; import ghidra.program.model.symbol.*; - public class MergeTwoProgramsScript extends GhidraScript { @Override protected void run() throws Exception { - if ( currentProgram == null ) { - printerr( "Please open a program first!" ); + if (currentProgram == null) { + printerr("Please open a program first!"); return; } - Program otherProgram = askProgram( "Select program from which to merge: " ); + Program otherProgram = askProgram("Select program from which to merge: "); - if ( otherProgram == null ) { - printerr( "Please select the other program first!" ); + if (otherProgram == null) { + printerr("Please select the other program first!"); return; } - if ( !currentProgram.getLanguage().equals( otherProgram.getLanguage() ) ) { - printerr( "Incompatible program languages!" ); - return; + try { + + if (!currentProgram.getLanguage().equals(otherProgram.getLanguage())) { + printerr("Incompatible program languages!"); + return; + } + + if (currentProgram.getMemory().intersects(otherProgram.getMemory())) { + printerr("Memory map of current program must be disjoint from other program!"); + return; + } + + openProgram(currentProgram); + + mergeMemory(currentProgram, otherProgram); + mergeSymbols(currentProgram, otherProgram); + mergeBookmarks(currentProgram, otherProgram); + mergeComments(currentProgram, otherProgram); + mergeData(currentProgram, otherProgram); + mergeInstructions(currentProgram, otherProgram); + mergeEquates(currentProgram, otherProgram); + mergeReferences(currentProgram, otherProgram); } - - if ( currentProgram.getMemory().intersects( otherProgram.getMemory() ) ) { - printerr( "Memory map of current program must be disjoint from other program!" ); - return; + finally { + otherProgram.release(this); } - - openProgram( currentProgram ); - - mergeMemory ( currentProgram, otherProgram ); - mergeSymbols ( currentProgram, otherProgram ); - mergeBookmarks ( currentProgram, otherProgram ); - mergeComments ( currentProgram, otherProgram ); - mergeData ( currentProgram, otherProgram ); - mergeInstructions( currentProgram, otherProgram ); - mergeEquates ( currentProgram, otherProgram ); - mergeReferences ( currentProgram, otherProgram ); } - private void mergeReferences( Program currProgram, Program otherProgram ) { - monitor.setMessage( "Merging references..." ); + private void mergeReferences(Program currProgram, Program otherProgram) { + monitor.setMessage("Merging references..."); ReferenceManager currentReferenceManager = currProgram.getReferenceManager(); ReferenceManager otherReferenceManager = otherProgram.getReferenceManager(); - ReferenceIterator otherReferenceIterator = otherReferenceManager.getReferenceIterator( otherProgram.getMinAddress() ); - while ( otherReferenceIterator.hasNext() ) { - if ( monitor.isCancelled() ) { + ReferenceIterator otherReferenceIterator = + otherReferenceManager.getReferenceIterator(otherProgram.getMinAddress()); + while (otherReferenceIterator.hasNext()) { + if (monitor.isCancelled()) { break; } Reference otherReference = otherReferenceIterator.next(); - if ( otherReference.isStackReference() ) { + if (otherReference.isStackReference()) { continue; } - currentReferenceManager.addReference( otherReference ); + currentReferenceManager.addReference(otherReference); } } - private void mergeInstructions( Program currProgram, Program otherProgram ) { - monitor.setMessage( "Merging instructions..." ); + private void mergeInstructions(Program currProgram, Program otherProgram) { + monitor.setMessage("Merging instructions..."); Listing currentListing = currProgram.getListing(); Listing otherListing = otherProgram.getListing(); - InstructionIterator otherInstructions = otherListing.getInstructions( true ); - while ( otherInstructions.hasNext() ) { - if ( monitor.isCancelled() ) { + InstructionIterator otherInstructions = otherListing.getInstructions(true); + while (otherInstructions.hasNext()) { + if (monitor.isCancelled()) { break; } Instruction otherInstruction = otherInstructions.next(); - if ( currentListing.isUndefined( otherInstruction.getMinAddress(), otherInstruction.getMaxAddress() ) ) { - disassemble( otherInstruction.getMinAddress() ); + if (currentListing.isUndefined(otherInstruction.getMinAddress(), + otherInstruction.getMaxAddress())) { + disassemble(otherInstruction.getMinAddress()); } } } - private void mergeEquates( Program currProgram, Program otherProgram ) throws Exception { - monitor.setMessage( "Merging equates..." ); + private void mergeEquates(Program currProgram, Program otherProgram) throws Exception { + monitor.setMessage("Merging equates..."); EquateTable currentEquateTable = currProgram.getEquateTable(); EquateTable otherEquateTable = otherProgram.getEquateTable(); Iterator otherEquates = otherEquateTable.getEquates(); - while ( otherEquates.hasNext() ) { - if ( monitor.isCancelled() ) { + while (otherEquates.hasNext()) { + if (monitor.isCancelled()) { break; } Equate otherEquate = otherEquates.next(); - Equate currentEquate = currentEquateTable.createEquate( otherEquate.getName(), otherEquate.getValue() ); - EquateReference [] otherEquateReferences = otherEquate.getReferences(); - for ( EquateReference otherEquateReference : otherEquateReferences ) { - if ( monitor.isCancelled() ) { + Equate currentEquate = + currentEquateTable.createEquate(otherEquate.getName(), otherEquate.getValue()); + EquateReference[] otherEquateReferences = otherEquate.getReferences(); + for (EquateReference otherEquateReference : otherEquateReferences) { + if (monitor.isCancelled()) { break; } - currentEquate.addReference( otherEquateReference.getAddress(), otherEquateReference.getOpIndex() ); + currentEquate.addReference(otherEquateReference.getAddress(), + otherEquateReference.getOpIndex()); } } } - private void mergeData( Program currProgram, Program otherProgram ) throws Exception { - monitor.setMessage( "Merging data..." ); + private void mergeData(Program currProgram, Program otherProgram) throws Exception { + monitor.setMessage("Merging data..."); Listing currentListing = currProgram.getListing(); Listing otherListing = otherProgram.getListing(); - DataIterator otherDataIterator = otherListing.getDefinedData( true ); - while ( otherDataIterator.hasNext() ) { - if ( monitor.isCancelled() ) { + DataIterator otherDataIterator = otherListing.getDefinedData(true); + while (otherDataIterator.hasNext()) { + if (monitor.isCancelled()) { break; } Data otherData = otherDataIterator.next(); - if ( currentListing.isUndefined( otherData.getMinAddress(), otherData.getMaxAddress() ) ) { - currentListing.createData( otherData.getMinAddress(), otherData.getDataType() ); + if (currentListing.isUndefined(otherData.getMinAddress(), otherData.getMaxAddress())) { + currentListing.createData(otherData.getMinAddress(), otherData.getDataType()); } } } - private void mergeComments( Program currProgram, Program otherProgram ) throws Exception { - monitor.setMessage( "Merging comments..." ); - int [] commentTypes = { - CodeUnit.EOL_COMMENT, - CodeUnit.PRE_COMMENT, - CodeUnit.POST_COMMENT, - CodeUnit.PLATE_COMMENT, - CodeUnit.REPEATABLE_COMMENT, - }; + private void mergeComments(Program currProgram, Program otherProgram) throws Exception { + monitor.setMessage("Merging comments..."); + int[] commentTypes = { CodeUnit.EOL_COMMENT, CodeUnit.PRE_COMMENT, CodeUnit.POST_COMMENT, + CodeUnit.PLATE_COMMENT, CodeUnit.REPEATABLE_COMMENT, }; Listing currentListing = currProgram.getListing(); Listing otherListing = otherProgram.getListing(); - CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits( true ); - while ( otherCodeUnits.hasNext() ) { - if ( monitor.isCancelled() ) { + CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits(true); + while (otherCodeUnits.hasNext()) { + if (monitor.isCancelled()) { break; } CodeUnit otherCodeUnit = otherCodeUnits.next(); - for ( int commentType : commentTypes ) { - if ( monitor.isCancelled() ) { + for (int commentType : commentTypes) { + if (monitor.isCancelled()) { break; } - String otherComment = otherCodeUnit.getComment( commentType ); - if ( otherComment != null ) { - currentListing.setComment( otherCodeUnit.getAddress(), commentType, otherComment ); + String otherComment = otherCodeUnit.getComment(commentType); + if (otherComment != null) { + currentListing.setComment(otherCodeUnit.getAddress(), commentType, + otherComment); } } } } - private void mergeBookmarks( Program currProgram, Program otherProgram ) { - monitor.setMessage( "Merging bookmarks..." ); + private void mergeBookmarks(Program currProgram, Program otherProgram) { + monitor.setMessage("Merging bookmarks..."); BookmarkManager currentBookmarkManager = currProgram.getBookmarkManager(); BookmarkManager otherBookmarkManager = otherProgram.getBookmarkManager(); Iterator otherBookmarks = otherBookmarkManager.getBookmarksIterator(); - while ( otherBookmarks.hasNext() ) { - if ( monitor.isCancelled() ) { + while (otherBookmarks.hasNext()) { + if (monitor.isCancelled()) { break; } Bookmark otherBookmark = otherBookmarks.next(); - currentBookmarkManager.setBookmark( otherBookmark.getAddress(), - otherBookmark.getTypeString(), - otherBookmark.getCategory(), - otherBookmark.getComment() ); + currentBookmarkManager.setBookmark(otherBookmark.getAddress(), + otherBookmark.getTypeString(), otherBookmark.getCategory(), + otherBookmark.getComment()); } } - private void mergeSymbols( Program currProgram, Program otherProgram ) throws Exception { - monitor.setMessage( "Merging symbols..." ); + private void mergeSymbols(Program currProgram, Program otherProgram) throws Exception { + monitor.setMessage("Merging symbols..."); SymbolTable currentSymbolTable = currProgram.getSymbolTable(); SymbolTable otherSymbolTable = otherProgram.getSymbolTable(); - SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols( false ); - while ( otherSymbols.hasNext() ) { - if ( monitor.isCancelled() ) { + SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols(false); + while (otherSymbols.hasNext()) { + if (monitor.isCancelled()) { break; } Symbol otherSymbol = otherSymbols.next(); - if ( otherSymbol.isDynamic() ) { + if (otherSymbol.isDynamic()) { continue; } try { Namespace otherNamespace = otherSymbol.getParentNamespace(); - Namespace currentNamespace = mirrorNamespace( currProgram, otherProgram, otherNamespace ); - if ( otherSymbol.getSymbolType() == SymbolType.FUNCTION ) { - Function otherFunction = otherProgram.getListing().getFunctionAt( otherSymbol.getAddress() ); - currProgram.getListing().createFunction( otherSymbol.getName(), - currentNamespace, - otherFunction.getEntryPoint(), - otherFunction.getBody(), - SourceType.USER_DEFINED ); + Namespace currentNamespace = + mirrorNamespace(currProgram, otherProgram, otherNamespace); + if (otherSymbol.getSymbolType() == SymbolType.FUNCTION) { + Function otherFunction = + otherProgram.getListing().getFunctionAt(otherSymbol.getAddress()); + currProgram.getListing() + .createFunction(otherSymbol.getName(), currentNamespace, + otherFunction.getEntryPoint(), otherFunction.getBody(), + SourceType.USER_DEFINED); } else { - currentSymbolTable.createLabel( otherSymbol.getAddress(), - otherSymbol.getName(), - currentNamespace, - SourceType.USER_DEFINED ); + currentSymbolTable.createLabel(otherSymbol.getAddress(), otherSymbol.getName(), + currentNamespace, SourceType.USER_DEFINED); } } - catch ( Exception e ) { - printerr( "Unable to create symbol: " + otherSymbol.getName() ); + catch (Exception e) { + printerr("Unable to create symbol: " + otherSymbol.getName()); } } } - private Namespace mirrorNamespace( Program currProgram, Program otherProgram, Namespace otherNamespace ) throws Exception { - if ( otherNamespace == null ) { + private Namespace mirrorNamespace(Program currProgram, Program otherProgram, + Namespace otherNamespace) throws Exception { + if (otherNamespace == null) { return currProgram.getGlobalNamespace(); } SourceType source = SourceType.USER_DEFINED;//this will be default, since we are running a script! try { source = otherNamespace.getSymbol().getSource(); } - catch ( Exception e ) { + catch (Exception e) { } return NamespaceUtils.createNamespaceHierarchy(otherNamespace.getName(true), null, currProgram, source); } - private void mergeMemory( Program currProgram, Program otherProgram ) throws Exception { - monitor.setMessage( "Merging memory..." ); + private void mergeMemory(Program currProgram, Program otherProgram) throws Exception { + monitor.setMessage("Merging memory..."); Memory otherMemory = otherProgram.getMemory(); MemoryBlock[] otherBlocks = otherMemory.getBlocks(); MessageLog log = new MessageLog(); diff --git a/Ghidra/Features/VersionTracking/ghidra_scripts/AutoVersionTrackingScript.java b/Ghidra/Features/VersionTracking/ghidra_scripts/AutoVersionTrackingScript.java index 69d9ad1939..3b82a56a54 100644 --- a/Ghidra/Features/VersionTracking/ghidra_scripts/AutoVersionTrackingScript.java +++ b/Ghidra/Features/VersionTracking/ghidra_scripts/AutoVersionTrackingScript.java @@ -17,7 +17,6 @@ // data and and then save the session. //@category Examples.Version Tracking -import java.util.Iterator; import java.util.List; import ghidra.app.script.GhidraScript; @@ -32,6 +31,21 @@ import ghidra.program.model.listing.Program; import ghidra.util.task.TaskLauncher; public class AutoVersionTrackingScript extends GhidraScript { + + private Program sourceProgram; + private Program destinationProgram; + + @Override + public void cleanup(boolean success) { + if (sourceProgram != null && sourceProgram.isUsedBy(this)) { + sourceProgram.release(this); + } + if (destinationProgram != null && destinationProgram.isUsedBy(this)) { + destinationProgram.release(this); + } + super.cleanup(success); + } + @Override public void run() throws Exception { @@ -39,9 +53,6 @@ public class AutoVersionTrackingScript extends GhidraScript { askProjectFolder("Please choose a folder for your Version Tracking session."); String name = askString("Please enter a Version Tracking session name", "Session Name"); - Program sourceProgram; - Program destinationProgram; - boolean isCurrentProgramSourceProg = askYesNo("Current Program Source Program?", "Is the current program your source program?"); @@ -54,6 +65,10 @@ public class AutoVersionTrackingScript extends GhidraScript { sourceProgram = askProgram("Please select the source (existing annotated) program"); } + if (sourceProgram == null || destinationProgram == null) { + return; + } + // Need to end the script transaction or it interferes with vt things that need locks end(true); @@ -81,9 +96,7 @@ public class AutoVersionTrackingScript extends GhidraScript { public static T getPlugin(PluginTool tool, Class c) { List list = tool.getManagedPlugins(); - Iterator it = list.iterator(); - while (it.hasNext()) { - Plugin p = it.next(); + for (Plugin p : list) { if (p.getClass() == c) { return c.cast(p); } diff --git a/Ghidra/Features/VersionTracking/ghidra_scripts/CreateAppliedExactMatchingSessionScript.java b/Ghidra/Features/VersionTracking/ghidra_scripts/CreateAppliedExactMatchingSessionScript.java index c518b47d9e..25f98b517b 100644 --- a/Ghidra/Features/VersionTracking/ghidra_scripts/CreateAppliedExactMatchingSessionScript.java +++ b/Ghidra/Features/VersionTracking/ghidra_scripts/CreateAppliedExactMatchingSessionScript.java @@ -17,6 +17,9 @@ // data and and then save the session. //@category Examples.Version Tracking +import java.util.Collection; +import java.util.List; + import ghidra.app.script.GhidraScript; import ghidra.feature.vt.api.correlator.program.*; import ghidra.feature.vt.api.db.VTSessionDB; @@ -31,17 +34,35 @@ import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Program; import ghidra.util.exception.CancelledException; -import java.util.Collection; -import java.util.List; - public class CreateAppliedExactMatchingSessionScript extends GhidraScript { + + private Program sourceProgram; + private Program destinationProgram; + + @Override + public void cleanup(boolean success) { + if (sourceProgram != null) { + sourceProgram.release(this); + } + if (destinationProgram != null) { + destinationProgram.release(this); + } + super.cleanup(success); + } + @Override public void run() throws Exception { DomainFolder folder = askProjectFolder("Please choose a folder for the session domain object"); String name = askString("Please enter a Version Tracking session name", "Session Name"); - Program sourceProgram = askProgram("Please select the source (existing annotated) program"); - Program destinationProgram = askProgram("Please select the destination (new) program"); + sourceProgram = askProgram("Please select the source (existing annotated) program"); + if (sourceProgram == null) { + return; + } + destinationProgram = askProgram("Please select the destination (new) program"); + if (destinationProgram == null) { + return; + } VTSession session = VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this); @@ -58,7 +79,8 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript { // should we have convenience methods in VTCorrelator that don't // take address sets, thus implying the entire address space should be used? - AddressSetView sourceAddressSet = sourceProgram.getMemory().getLoadedAndInitializedAddressSet(); + AddressSetView sourceAddressSet = + sourceProgram.getMemory().getLoadedAndInitializedAddressSet(); AddressSetView destinationAddressSet = destinationProgram.getMemory().getLoadedAndInitializedAddressSet(); @@ -91,17 +113,16 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript { private void correlateAndPossiblyApply(Program sourceProgram, Program destinationProgram, VTSession session, PluginTool serviceProvider, VTAssociationManager manager, AddressSetView sourceAddressSet, AddressSetView destinationAddressSet, - VTProgramCorrelatorFactory factory) throws CancelledException, - VTAssociationStatusException { + VTProgramCorrelatorFactory factory) + throws CancelledException, VTAssociationStatusException { AddressSetView restrictedSourceAddresses = excludeAcceptedMatches(session, sourceAddressSet, true); AddressSetView restrictedDestinationAddresses = excludeAcceptedMatches(session, destinationAddressSet, false); VTOptions options = factory.createDefaultOptions(); - VTProgramCorrelator correlator = - factory.createCorrelator(serviceProvider, sourceProgram, restrictedSourceAddresses, - destinationProgram, restrictedDestinationAddresses, options); + VTProgramCorrelator correlator = factory.createCorrelator(serviceProvider, sourceProgram, + restrictedSourceAddresses, destinationProgram, restrictedDestinationAddresses, options); VTMatchSet results = correlator.correlate(session, monitor); applyMatches(manager, results.getMatches()); diff --git a/Ghidra/Features/VersionTracking/ghidra_scripts/FindChangedFunctionsScript.java b/Ghidra/Features/VersionTracking/ghidra_scripts/FindChangedFunctionsScript.java index 044ac06bf2..11e6e4db18 100644 --- a/Ghidra/Features/VersionTracking/ghidra_scripts/FindChangedFunctionsScript.java +++ b/Ghidra/Features/VersionTracking/ghidra_scripts/FindChangedFunctionsScript.java @@ -29,26 +29,47 @@ import ghidra.util.Msg; public class FindChangedFunctionsScript extends GhidraVersionTrackingScript { + private Program p1; + private Program p2; + + @Override + public void cleanup(boolean success) { + if (p1 != null) { + p1.release(this); + } + if (p2 != null) { + p2.release(this); + } + super.cleanup(success); + } + @Override protected void run() throws Exception { Project project = state.getProject(); if (project == null) { throw new RuntimeException("No project open"); } - + // Prompt the user to load the two programs that will be analyzed. // This will only allow you to select programs from the currently-open // project in Ghidra, so import them if you haven't already. - Program p1 = askProgram("Program1_Version1"); - Program p2 = askProgram("Program1_Version2"); - + p1 = askProgram("Program1_Version1"); + if (p1 == null) { + return; + } + p2 = askProgram("Program1_Version2"); + if (p2 == null) { + return; + } + // Make sure the selected programs are not open and locked by Ghidra. If so, // warn the user. if (areProgramsLocked(p1, p2)) { - Msg.showError(this, null, "Program is locked!", "One of the programs you selected is locked by Ghidra. Please correct and try again."); + Msg.showError(this, null, "Program is locked!", + "One of the programs you selected is locked by Ghidra. Please correct and try again."); return; - } - + } + // Create a new VT session createVersionTrackingSession("new session", p1, p2); @@ -67,7 +88,7 @@ public class FindChangedFunctionsScript extends GhidraVersionTrackingScript { println("Did not find exact match for: " + functionName); } } - + /** * Returns true if one of the programs is locked. *

    diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java index 6522d5954b..47e3c8e2d7 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java @@ -82,7 +82,7 @@ public class AutoVersionTrackingTask extends Task { private static int NUM_CORRELATORS = 8; /** - * Constructor for AutoVersionTrackingCommand + * Constructor for a modal/blocking AutoVersionTrackingTask * * @param controller The Version Tracking controller for this session containing option and * tool information needed for this command. @@ -483,8 +483,6 @@ public class AutoVersionTrackingTask extends Task { continue; } - - // remove any matches that have identical source functions - if more than one // with exactly the same instructions and operands then cannot determine a unique match Set

    sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBWithUserDataContentHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBWithUserDataContentHandler.java index 84738a3077..053745e820 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBWithUserDataContentHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DBWithUserDataContentHandler.java @@ -73,7 +73,7 @@ public abstract class DBWithUserDataContentHandler fileTrackingMap = + Collections.synchronizedMap(new IdentityHashMap<>()); + + // Names of folders that stores project data public static final String MANGLED_DATA_FOLDER_NAME = "data"; public static final String INDEXED_DATA_FOLDER_NAME = "idata"; public static final String USER_FOLDER_NAME = "user"; @@ -77,14 +86,17 @@ public class ProjectFileManager implements ProjectData { private RootGhidraFolderData rootFolderData; - private Map openDomainObjects = - new HashMap<>(); + private Map openDomainObjects = new HashMap<>(); private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter(); private ProjectLock projectLock; private String owner; + private int inUseCount = 0; // open file count plus active merge sessions + private boolean closed = false; + private boolean disposed = false; + /** * Constructor for existing projects. * @param localStorageLocator the location of the project @@ -96,7 +108,7 @@ public class ProjectFileManager implements ProjectData { * write lock (i.e., project in-use) * @throws FileNotFoundException if project directory not found */ - public ProjectFileManager(ProjectLocator localStorageLocator, boolean isInWritableProject, + public DefaultProjectData(ProjectLocator localStorageLocator, boolean isInWritableProject, boolean resetOwner) throws NotOwnerException, IOException, LockException { this.localStorageLocator = localStorageLocator; @@ -142,7 +154,7 @@ public class ProjectFileManager implements ProjectData { * @throws LockException if {@code isInWritableProject} is true and unable to establish project * lock (i.e., project in-use) */ - public ProjectFileManager(ProjectLocator localStorageLocator, RepositoryAdapter repository, + public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository, boolean isInWritableProject) throws IOException, LockException { this.localStorageLocator = localStorageLocator; this.repository = repository; @@ -170,7 +182,7 @@ public class ProjectFileManager implements ProjectData { * @param versionedFileSystem an existing versioned file-system * @throws IOException if an IO error occurs */ - ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem) + DefaultProjectData(LocalFileSystem fileSystem, FileSystem versionedFileSystem) throws IOException { this.localStorageLocator = new ProjectLocator(null, "Test"); owner = SystemUtilities.getUserName(); @@ -530,7 +542,7 @@ public class ProjectFileManager implements ProjectData { /** * Returns the owner of the project that is associated with this - * ProjectFileManager. A value of null indicates an old multiuser + * DefaultProjectData. A value of null indicates an old multiuser * project. * @return the owner of the project */ @@ -731,9 +743,8 @@ public class ProjectFileManager implements ProjectData { @Override public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force, - TaskMonitor monitor) - throws IOException, CancelledException { - + TaskMonitor monitor) throws IOException, CancelledException { + newRepository.connect(); if (!newRepository.isConnected()) { throw new IOException("new respository not connected"); @@ -761,8 +772,8 @@ public class ProjectFileManager implements ProjectData { long checkoutId = item.getCheckoutId(); int checkoutVersion = item.getCheckoutVersion(); - ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout( - df.getParent().getPathname(), df.getName(), checkoutId); + ItemCheckoutStatus otherCheckoutStatus = + newRepository.getCheckout(df.getParent().getPathname(), df.getName(), checkoutId); if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) { return true; @@ -793,6 +804,7 @@ public class ProjectFileManager implements ProjectData { * @throws IOException if IO error occurs * @throws CancelledException if task cancelled */ + @Override public boolean hasInvalidCheckouts(List checkoutList, RepositoryAdapter newRepository, TaskMonitor monitor) throws IOException, CancelledException { @@ -856,6 +868,7 @@ public class ProjectFileManager implements ProjectData { * @throws IOException if IO error occurs * @throws CancelledException if task cancelled */ + @Override public List findCheckedOutFiles(TaskMonitor monitor) throws IOException, CancelledException { List list = new ArrayList<>(); @@ -864,8 +877,7 @@ public class ProjectFileManager implements ProjectData { } private void findCheckedOutFiles(String folderPath, List checkoutList, - TaskMonitor monitor) - throws IOException, CancelledException { + TaskMonitor monitor) throws IOException, CancelledException { for (String name : fileSystem.getItemNames(folderPath)) { monitor.checkCancelled(); @@ -902,6 +914,30 @@ public class ProjectFileManager implements ProjectData { } } + @Override + public URL getSharedProjectURL() { + URL projectURL = localStorageLocator.getURL(); + if (!GhidraURL.isServerRepositoryURL(projectURL)) { + if (repository == null) { + return null; + } + // NOTE: only supports ghidra protocol without extension protocol. + // Assumes any extension protocol use would be reflected in ProjectLocator URL. + ServerInfo serverInfo = repository.getServerInfo(); + projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(), + repository.getName()); + } + return projectURL; + } + + @Override + public URL getLocalProjectURL() { + if (!localStorageLocator.isTransient()) { + return localStorageLocator.getURL(); + } + return null; + } + /** * Returns the standard user data filename associated with the specified file ID. * @param associatedFileID the file id @@ -934,7 +970,7 @@ public class ProjectFileManager implements ProjectData { } userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> { - synchronized (ProjectFileManager.this) { + synchronized (DefaultProjectData.this) { startReconcileUserDataFiles(); } }); @@ -1184,14 +1220,64 @@ public class ProjectFileManager implements ProjectData { return projectDir; } + public synchronized boolean isClosed() { + return closed; + } + + public synchronized boolean isDisposed() { + return disposed; + } + @Override public void close() { + synchronized (this) { + if (!closed) { + Msg.debug(this, "Closing ProjectData: " + projectDir); + closed = true; + } + if (inUseCount != 0) { + return; // delay dispose + } + } dispose(); } - public void dispose() { + private synchronized void incrementInUseCount() { + ++inUseCount; + } + + private void decrementInUseCount() { + synchronized (this) { + if (inUseCount <= 0) { + Msg.error(this, "DefaultProjectData in-use tracking is out-of-sync: " + projectDir); + } + if (--inUseCount > 0 || !closed) { + return; + } + } + dispose(); + } + + /** + * Immediately dispose this project data store instance. If this project has an associated + * {@link RepositoryAdapter} it will be disconnected as well. This method should generally not + * be used directly when there may be open {@link DomainObject} instances which may rely + * on an associated server connection. The {@link #clone()} method should be used when + * open {@link DomainObject} instances may exist and should be allowed to persist until + * they are closed. + */ + protected void dispose() { synchronized (this) { + if (disposed) { + return; + } + + Msg.debug(this, "Disposing ProjectData: " + projectDir); + + closed = true; + disposed = true; + if (userDataReconcileTimer != null) { userDataReconcileTimer.stop(); } @@ -1255,7 +1341,7 @@ public class ProjectFileManager implements ProjectData { /** * Returns the open domain object (opened for update) for the specified path. * @param pathname the path name - * @return the domain object + * @return the domain object or null if not open */ synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) { return openDomainObjects.get(pathname); @@ -1298,4 +1384,55 @@ public class ProjectFileManager implements ProjectData { public TaskMonitor getProjectDisposalMonitor() { return projectDisposalMonitor; } + + /** + * Signals the start of a complex merge operation. + * The {@link #mergeEnded()} must be invoked after this method invocation when the + * merge operation has completed. + */ + void mergeStarted() { + incrementInUseCount(); + } + + /** + * Signals the completion of a complex merge operation (see {@link #mergeStarted()}). + */ + void mergeEnded() { + decrementInUseCount(); + } + + /** + * Signals that a non-link file has been opened as the specified + * {@link DomainObjectAdapter doa} from this project data store and should be + * tracked. This will delay disposal of this object until the specified domain object is + * either closed or saved to a different project store (i.e., hand-off operation). + * It is important that this method not be invoked when opening a link-file + * since it is the referenced file being opened that must be tracked and not the + * opening of the link-file itself. + * @param doa domain object + */ + void trackDomainFileInUse(DomainObjectAdapter doa) { + DefaultProjectData projectData = fileTrackingMap.put(doa, this); + if (projectData == this) { + return; // no change in associated project + } + + if (projectData != null) { + projectData.decrementInUseCount(); + } + else { + doa.addCloseListener(dobj -> domainObjectClosed(dobj)); + } + + incrementInUseCount(); + } + + private static void domainObjectClosed(DomainObject dobj) { + + DefaultProjectData projectData = fileTrackingMap.remove(dobj); + if (projectData != null) { + projectData.decrementInUseCount(); + } + } + } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java index daf20ea028..a4052a91f6 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileIndex.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,10 +28,10 @@ import java.util.HashMap; */ class DomainFileIndex implements DomainFolderChangeListener { - private ProjectFileManager projectData; + private DefaultProjectData projectData; private HashMap fileIdToPathIndex = new HashMap(); - DomainFileIndex(ProjectFileManager projectData) { + DomainFileIndex(DefaultProjectData projectData) { this.projectData = projectData; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java index dd71ba1b10..3c2fef7ed5 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainFileProxy.java @@ -23,14 +23,11 @@ import java.util.*; import javax.swing.Icon; -import org.apache.commons.lang3.StringUtils; - import ghidra.framework.client.*; import ghidra.framework.model.*; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.remote.RepositoryItem; -import ghidra.framework.store.ItemCheckoutStatus; -import ghidra.framework.store.Version; +import ghidra.framework.store.*; import ghidra.framework.store.db.PackedDatabase; import ghidra.util.InvalidNameException; import ghidra.util.ReadOnlyException; @@ -120,11 +117,13 @@ public class DomainFileProxy implements DomainFile { private URL getSharedFileURL(URL sharedProjectURL, String ref) { try { - String spec = getPathname().substring(1); // remove leading '/' - if (!StringUtils.isEmpty(ref)) { - spec += "#" + ref; + // Direct URL construction done so that ghidra protocol extension may be supported + String urlStr = sharedProjectURL.toExternalForm(); + if (urlStr.endsWith(FileSystem.SEPARATOR)) { + urlStr = urlStr.substring(0, urlStr.length() - 1); } - return new URL(sharedProjectURL, spec); + urlStr += getPathname(); + return new URL(urlStr); } catch (MalformedURLException e) { // ignore @@ -136,12 +135,12 @@ public class DomainFileProxy implements DomainFile { if (properties == null) { return null; } - String serverName = properties.getProperty(ProjectFileManager.SERVER_NAME); - String repoName = properties.getProperty(ProjectFileManager.REPOSITORY_NAME); + String serverName = properties.getProperty(DefaultProjectData.SERVER_NAME); + String repoName = properties.getProperty(DefaultProjectData.REPOSITORY_NAME); if (serverName == null || repoName == null) { return null; } - int port = Integer.parseInt(properties.getProperty(ProjectFileManager.PORT_NUMBER, "0")); + int port = Integer.parseInt(properties.getProperty(DefaultProjectData.PORT_NUMBER, "0")); if (!ClientUtil.isConnected(serverName, port)) { return null; // avoid initiating a server connection. @@ -187,7 +186,7 @@ public class DomainFileProxy implements DomainFile { return getSharedFileURL(projectURL, ref); } Properties properties = - ProjectFileManager.readProjectProperties(projectLocation.getProjectDir()); + DefaultProjectData.readProjectProperties(projectLocation.getProjectDir()); return getSharedFileURL(properties, ref); } return null; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java index 58c161c7f8..6a02a0c234 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapter.java @@ -54,8 +54,7 @@ public abstract class DomainObjectAdapter implements DomainObject { protected Map changeSupportMap = new ConcurrentHashMap(); private volatile boolean eventsEnabled = true; - private Set closeListeners = - new CopyOnWriteArraySet(); + private Set closeListeners = new CopyOnWriteArraySet<>(); private ArrayList consumers; protected Map metadata = new LinkedHashMap(); @@ -210,7 +209,7 @@ public abstract class DomainObjectAdapter implements DomainObject { private void notifyCloseListeners() { for (DomainObjectClosedListener listener : closeListeners) { - listener.domainObjectClosed(); + listener.domainObjectClosed(this); } closeListeners.clear(); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java index 30034fc122..21c1143b39 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFile.java @@ -33,7 +33,7 @@ public class GhidraFile implements DomainFile { // FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces - protected ProjectFileManager fileManager; + protected DefaultProjectData projectData; private LocalFileSystem fileSystem; private DomainFolderChangeListener listener; @@ -45,13 +45,13 @@ public class GhidraFile implements DomainFile { this.parent = parent; this.name = name; - this.fileManager = parent.getProjectFileManager(); + this.projectData = parent.getProjectData(); this.fileSystem = parent.getLocalFileSystem(); this.listener = parent.getChangeListener(); } public LocalFileSystem getUserFileSystem() { - return fileManager.getUserFileSystem(); + return projectData.getUserFileSystem(); } private GhidraFileData getFileData() throws FileNotFoundException, IOException { @@ -97,8 +97,8 @@ public class GhidraFile implements DomainFile { void clearDomainObj() { String path = getPathname(); - DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path); - if (doa != null && fileManager.clearDomainObject(getPathname())) { + DomainObjectAdapter doa = projectData.getOpenedDomainObject(path); + if (doa != null && projectData.clearDomainObject(getPathname())) { listener.domainFileObjectClosed(this, doa); } } @@ -120,7 +120,7 @@ public class GhidraFile implements DomainFile { @Override public ProjectLocator getProjectLocator() { - return fileManager.getProjectLocator(); + return projectData.getProjectLocator(); } @Override @@ -215,10 +215,10 @@ public class GhidraFile implements DomainFile { @Override public DomainObject getOpenedDomainObject(Object consumer) { - DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname()); + DomainObjectAdapter domainObj = projectData.getOpenedDomainObject(getPathname()); if (domainObj != null) { if (!domainObj.addConsumer(consumer)) { - fileManager.clearDomainObject(getPathname()); + projectData.clearDomainObject(getPathname()); throw new IllegalStateException("Domain Object is closed: " + domainObj.getName()); } } @@ -248,7 +248,7 @@ public class GhidraFile implements DomainFile { @Override public void save(TaskMonitor monitor) throws IOException, CancelledException { - DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); + DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname()); if (dobj == null) { throw new AssertException("Cannot save, domainObj not open"); } @@ -263,7 +263,7 @@ public class GhidraFile implements DomainFile { @Override public boolean canSave() { - DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); + DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname()); if (dobj == null) { return false; } @@ -573,7 +573,7 @@ public class GhidraFile implements DomainFile { @Override public ArrayList getConsumers() { - DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); + DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname()); if (dobj == null) { return new ArrayList<>(); } @@ -582,13 +582,13 @@ public class GhidraFile implements DomainFile { @Override public boolean isChanged() { - DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); + DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname()); return dobj != null && dobj.isChanged(); } @Override public boolean isOpen() { - return fileManager.getOpenedDomainObject(getPathname()) != null; + return projectData.getOpenedDomainObject(getPathname()) != null; } @Override @@ -640,7 +640,7 @@ public class GhidraFile implements DomainFile { return false; } GhidraFile other = (GhidraFile) obj; - if (fileManager != other.fileManager) { + if (projectData != other.projectData) { return false; } return getPathname().equals(other.getPathname()); @@ -653,11 +653,11 @@ public class GhidraFile implements DomainFile { @Override public String toString() { - ProjectLocator projectLocator = parent.getProjectData().getProjectLocator(); + ProjectLocator projectLocator = projectData.getProjectLocator(); if (projectLocator.isTransient()) { - return fileManager.getProjectLocator().getName() + getPathname(); + return projectLocator.getName() + getPathname(); } - return fileManager.getProjectLocator().getName() + ":" + getPathname(); + return projectLocator.getName() + ":" + getPathname(); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java index 370cb130e8..29b3d70f0c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java @@ -21,6 +21,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.Icon; @@ -44,6 +45,16 @@ import ghidra.util.task.TaskMonitor; import resources.MultiIcon; import resources.icons.TranslateIcon; +/** + * {@link GhidraFileData} provides the managed object which represents a project file that + * corresponds to matched {@link FolderItem} pair across both a versioned and private + * filesystem and viewed as a single file at the project level. This class closely mirrors the + * {@link DomainFile} interface and is used by the {@link GhidraFile} implementation; both of which + * represent immutable file references. Changes made to this file's name or path are not reflected + * in old {@link DomainFile} instances and must be re-instantiated following such a change. + * Any long-term retention of {@link DomainFolder} and {@link DomainFile} instances requires an + * appropriate change listener to properly discard/reacquire such instances. + */ public class GhidraFileData { static final int ICON_WIDTH = 18; @@ -62,7 +73,7 @@ public class GhidraFileData { public static final Icon NOT_LATEST_CHECKED_OUT_ICON = new GIcon("icon.project.data.file.ghidra.not.latest"); //@formatter:on - private ProjectFileManager fileManager; + private DefaultProjectData projectData; private LocalFileSystem fileSystem; private FileSystem versionedFileSystem; private DomainFolderChangeListener listener; @@ -77,18 +88,24 @@ public class GhidraFileData { private Icon icon; private Icon disabledIcon; - private volatile boolean busy = false; + private AtomicBoolean busy = new AtomicBoolean(); // TODO: Many of the old methods assumed that the state was up-to-date due to // refreshing ... we are relying on non-refreshed data to be dropped from cache map and no // longer used. + /** + * Construct a file instance with a specified name and a correpsonding parent folder + * @param parent parent folder + * @param name file name + * @throws IOException if an IO error occurs + */ GhidraFileData(GhidraFolderData parent, String name) throws IOException { this.parent = parent; this.name = name; - this.fileManager = parent.getProjectFileManager(); + this.projectData = parent.getProjectData(); this.fileSystem = parent.getLocalFileSystem(); this.versionedFileSystem = parent.getVersionedFileSystem(); this.listener = parent.getChangeListener(); @@ -127,6 +144,10 @@ public class GhidraFileData { return fileIdWasNull && fileID != null; } + /** + * Notification callback that this file's status may have changed + * @throws IOException if IO error occurs + */ void statusChanged() throws IOException { statusChanged(false); } @@ -159,35 +180,55 @@ public class GhidraFileData { } } + /** + * Perform file in-use / busy check + * @throws FileInUseException if file is in-use or busy + */ void checkInUse() throws FileInUseException { synchronized (fileSystem) { - if (busy || getOpenedDomainObject() != null) { + if (busy.get() || getOpenedDomainObject() != null) { throw new FileInUseException(name + " is in use"); } } } + /** + * Returns true if the domain object in this domain file exists and has an open transaction. + * @return true if busy + */ boolean isBusy() { - if (busy) { + if (busy.get()) { return true; } DomainObjectAdapter dobj = getOpenedDomainObject(); return dobj != null && !dobj.canLock(); } + /** + * Removes this file from file-index maintained by {@link ProjectData} instance + * following its removal from the project. + */ void dispose() { - fileManager.removeFromIndex(fileID); + projectData.removeFromIndex(fileID); // NOTE: clearing the following can cause issues since there may be some residual // activity/use which will get a NPE // parent = null; -// fileManager = null; +// projectData = null; // listener = null; } + /** + * Returns a unique file-ID + * @return the ID + */ String getFileID() { return fileID; } + /** + * Returns the path name to the domain object. + * @return the path name + */ String getPathname() { String path = parent.getPathname(); if (path.length() != FileSystem.SEPARATOR.length()) { @@ -197,14 +238,25 @@ public class GhidraFileData { return path; } + /** + * Get the name of this project file + * @return the name + */ String getName() { return name; } + /** + * Get the parent folder for this file. + * @return the parent + */ GhidraFolderData getParent() { return parent; } + /** + * @return {@link DomainFile} instance which corresponds to this file. + */ GhidraFile getDomainFile() { return new GhidraFile(parent.getDomainFolder(), name); } @@ -217,7 +269,7 @@ public class GhidraFileData { */ URL getSharedProjectURL(String ref) { synchronized (fileSystem) { - RepositoryAdapter repository = parent.getProjectFileManager().getRepository(); + RepositoryAdapter repository = projectData.getRepository(); if (versionedFolderItem != null && repository != null) { URL folderURL = parent.getDomainFolder().getSharedProjectURL(); try { @@ -268,6 +320,16 @@ public class GhidraFileData { } } + /** + * Set the name on this file. + * @param newName domain file name + * @return renamed domain file (older DomainFile instances becomes invalid since they are immutable) + * @throws InvalidNameException if newName contains illegal characters + * @throws DuplicateFileException if a file named newName + * already exists in this files domain folder. + * @throws FileInUseException if this file is in-use / checked-out. + * @throws IOException if an IO or access error occurs. + */ GhidraFile setName(String newName) throws InvalidNameException, IOException { synchronized (fileSystem) { if (fileSystem.isReadOnly()) { @@ -310,6 +372,11 @@ public class GhidraFileData { } } + /** + * Returns content-type string for this file + * @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT} + * or {@link ContentHandler#UNKNOWN_CONTENT}. + */ String getContentType() { synchronized (fileSystem) { FolderItem item = folderItem != null ? folderItem : versionedFolderItem; @@ -324,7 +391,7 @@ public class GhidraFileData { } /** - * Get content handler + * Get content handler for this file * @return content handler * @throws IOException if an IO error occurs, file not found, or unsupported content */ @@ -340,6 +407,10 @@ public class GhidraFileData { } } + /** + * Returns the underlying Class for the domain object in this domain file. + * @return the class or null if does not correspond to a domain object. + */ Class getDomainObjectClass() { synchronized (fileSystem) { try { @@ -352,6 +423,16 @@ public class GhidraFileData { } } + /** + * Returns changes made to versioned file by others since checkout was performed. + * NOTE: This method is unable to cope with version issues which may require an + * upgrade. + * @return change set or null + * @throws VersionException latest version was created with a different version of software + * which prevents rapid determination of change set. + * @throws IOException if a folder item access error occurs or change set was + * produced by newer version of software and can not be read + */ ChangeSet getChangesByOthersSinceCheckout() throws VersionException, IOException { synchronized (fileSystem) { if (versionedFolderItem != null && folderItem != null && folderItem.isCheckedOut()) { @@ -363,10 +444,38 @@ public class GhidraFileData { } } + /** + * Returns the domainObject for this DomainFile only if it is already open. + * @return the already opened domainObject or null if it is not currently open. + */ private DomainObjectAdapter getOpenedDomainObject() { - return fileManager.getOpenedDomainObject(getPathname()); + return projectData.getOpenedDomainObject(getPathname()); } + /** + * Opens and returns the current domain object. If the domain object is already opened, + * then the existing open domain object is returned. + * @param consumer consumer of the domain object which is responsible for + * releasing it after use. When all the consumers using the domain object release it, then + * the object is closed and its resources released. + * @param okToUpgrade if true, allows the system to upgrade out of data domain objects to + * be in compliance with the current version of Ghidra. A Version exception will be thrown + * if the domain object cannot be upgraded OR okToUpgrade is false and the domain object is + * out of date. + * @param okToRecover if true, allows the system to recover unsaved file changes which + * resulted from a crash. If false, any existing recovery data will be deleted. + * This flag is only relevant if project is open for update (isInProject) and the file can be + * opened for update. + * @param monitor permits monitoring of open progress. + * @return an open domain object can be modified and saved. (Not read-only) + * @throws VersionException if the domain object could not be read due + * to a version format change. If okToUpgrade is true, then a VersionException indicates + * that the domain object cannot be upgraded to the current format. If okToUpgrade is false, + * then the VersionException only means the object is not in the current format - it + * may or may not be possible to upgrade. + * @throws IOException if an IO or access error occurs. + * @throws CancelledException if monitor cancelled operation + */ DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover, TaskMonitor monitor) throws VersionException, IOException, CancelledException { FolderItem myFolderItem; @@ -379,7 +488,7 @@ public class GhidraFileData { if (domainObj != null) { if (!domainObj.addConsumer(consumer)) { domainObj = null; - fileManager.clearDomainObject(getPathname()); + projectData.clearDomainObject(getPathname()); } else { return domainObj; @@ -399,7 +508,13 @@ public class GhidraFileData { domainObj = ch.getDomainObject(myFolderItem, parent.getUserFileSystem(), FolderItem.DEFAULT_CHECKOUT_ID, okToUpgrade, okToRecover, consumer, monitor); - fileManager.setDomainObject(getPathname(), domainObj); + projectData.setDomainObject(getPathname(), domainObj); + + // Notify file manager of in-use domain object. + // A link-file object is indirect with tracking intiated by the URL-referenced file. + if (!isLinkFile()) { + projectData.trackDomainFileInUse(domainObj); + } } // Set domain file for newly opened domain object @@ -410,7 +525,7 @@ public class GhidraFileData { } catch (Exception e) { domainObj.release(consumer); - fileManager.clearDomainObject(getPathname()); + projectData.clearDomainObject(getPathname()); // generate IOException Throwable cause = e.getCause(); if (cause instanceof IOException) { @@ -425,6 +540,24 @@ public class GhidraFileData { return domainObj; } + /** + * Returns a "read-only" version of the domain object. "Read-only" means that the domain + * object cannot be saved back into its original domain object. It can still be modified + * and saved to a new domain file. The domain object will be assigned a temporary domain + * file that will not allow a "save" operation. The user must do a "save as" + * to a new filename. + * @param consumer consumer of the domain object which is responsible for + * releasing it after use. + * @param version the domain object version requested. DEFAULT_VERSION should be + * specified to open the current version. + * @param monitor permits monitoring of open progress. + * @return a new domain object that is disassociated from its original domain file. + * @throws VersionException if the domain object could not be read due + * to a version format change. + * @throws FileNotFoundException if the stored file/version was not found. + * @throws IOException if an IO or access error occurs. + * @throws CancelledException if monitor cancelled operation + */ DomainObject getReadOnlyDomainObject(Object consumer, int version, TaskMonitor monitor) throws VersionException, IOException, CancelledException { synchronized (fileSystem) { @@ -435,6 +568,12 @@ public class GhidraFileData { DomainObjectAdapter doa = ch.getReadOnlyObject(item, version, true, consumer, monitor); doa.setChanged(false); + // Notify file manager of in-use domain object. + // A link-file object is indirect with tracking intiated by the URL-referenced file. + if (!isLinkFile()) { + projectData.trackDomainFileInUse(doa); + } + DomainFileProxy proxy = new DomainFileProxy(name, getParent().getPathname(), doa, version, fileID, parent.getProjectLocator()); proxy.setLastModified(getLastModifiedTime()); @@ -442,6 +581,24 @@ public class GhidraFileData { } } + /** + * Returns a new DomainObject that cannot be changed or saved to its original file. + * NOTE: The use of this method should generally be avoided since it can't + * handle version changes that may have occured and require a data upgrade + * (e.g., DB schema change). + * @param consumer consumer of the domain object which is responsible for + * releasing it after use. + * @param version the domain object version requested. DEFAULT_VERSION should be + * specified to open the current version. + * @param monitor permits monitoring of open progress. + * @return a new domain object that is disassociated from its original domain file + * and cannot be modified + * @throws VersionException if the domain object could not be read due + * to a version format change. + * @throws FileNotFoundException if the stored file/version was not found. + * @throws IOException if an IO or access error occurs. + * @throws CancelledException if monitor cancelled operation + */ DomainObject getImmutableDomainObject(Object consumer, int version, TaskMonitor monitor) throws VersionException, IOException, CancelledException { synchronized (fileSystem) { @@ -454,6 +611,13 @@ public class GhidraFileData { else { obj = ch.getImmutableObject(versionedFolderItem, consumer, version, -1, monitor); } + + // Notify file manager of in-use domain object. + // A link-file object is indirect with tracking intiated by the URL-referenced file. + if (!isLinkFile()) { + projectData.trackDomainFileInUse(obj); + } + DomainFileProxy proxy = new DomainFileProxy(name, getParent().getPathname(), obj, version, fileID, parent.getProjectLocator()); proxy.setLastModified(getLastModifiedTime()); @@ -461,6 +625,11 @@ public class GhidraFileData { } } + /** + * Prior to invoking getDomainObject, this method can be used to determine if + * unsaved changes can be recovered on the next open. + * @return true if recovery data exists. + */ boolean canRecover() { synchronized (fileSystem) { DomainObjectAdapter dobj = getOpenedDomainObject(); @@ -471,21 +640,25 @@ public class GhidraFileData { } } + /** + * If the file has an updatable domain object with unsaved changes, generate a recovery + * snapshot. + * @return true if snapshot successful or not needed, false if file is busy which prevents + * snapshot, or snapshot was cancelled. + * @throws IOException if there is an exception saving the snapshot + */ boolean takeRecoverySnapshot() throws IOException { if (fileSystem.isReadOnly()) { return true; } - DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); + DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname()); if (!(dobj instanceof DomainObjectAdapterDB) || !dobj.isChanged()) { return true; } LockingTaskMonitor monitor = null; DomainObjectAdapterDB dbObjDB = (DomainObjectAdapterDB) dobj; - synchronized (fileSystem) { - if (busy) { - return true; - } - busy = true; + if (busy.getAndSet(true)) { + return false; // snapshot must be postponed } try { monitor = dbObjDB.lockForSnapshot(true, "Recovery Snapshot Task"); @@ -499,15 +672,17 @@ public class GhidraFileData { return false; } finally { - synchronized (fileSystem) { - busy = false; - } + busy.set(false); if (monitor != null) { monitor.releaseLock(); // releases lock } } } + /** + * Get a long value representing the time when the data was last modified. + * @return the time + */ long getLastModifiedTime() { synchronized (fileSystem) { if (folderItem != null) { @@ -520,6 +695,12 @@ public class GhidraFileData { } } + /** + * Get the state based Icon image for the domain file based upon its content class. + * @param disabled true if the icon return should be rendered as + * not enabled + * @return image icon + */ Icon getIcon(boolean disabled) { if (disabled) { if (disabledIcon == null) { @@ -602,17 +783,29 @@ public class GhidraFileData { return DomainFile.UNSUPPORTED_FILE_ICON; } + /** + * Return whether the domain object in this domain file has changed. + * @return true if changed + */ boolean isChanged() { DomainObjectAdapter dobj = getOpenedDomainObject(); return dobj != null && dobj.isChanged(); } + /** + * Returns true if this is a checked-out file. + * @return true if checked-out + */ boolean isCheckedOut() { synchronized (fileSystem) { return folderItem != null && folderItem.isCheckedOut(); } } + /** + * Returns true if this a checked-out file with exclusive access. + * @return true if checked-out exclusively + */ boolean isCheckedOutExclusive() { synchronized (fileSystem) { if (folderItem == null) { @@ -626,6 +819,10 @@ public class GhidraFileData { } } + /** + * Returns true if this is a checked-out file which has been modified since it was checked-out. + * @return true if modified since check-out + */ boolean modifiedSinceCheckout() { synchronized (fileSystem) { return isCheckedOut() && @@ -633,12 +830,21 @@ public class GhidraFileData { } } + /** + * Returns whether the object is read-only. From a framework point of view a read-only object + * can never be changed. + * @return true if read-only + */ boolean isReadOnly() { synchronized (fileSystem) { return folderItem != null && folderItem.isReadOnly(); } } + /** + * Return true if this is a versioned database, else false + * @return true if versioned + */ boolean isVersioned() { synchronized (fileSystem) { if (versionedFolderItem == null) { @@ -648,12 +854,20 @@ public class GhidraFileData { } } + /** + * Returns true if the file is versioned but a private copy also exists. + * @return true if hijacked + */ boolean isHijacked() { synchronized (fileSystem) { return folderItem != null && versionedFolderItem != null && !folderItem.isCheckedOut(); } } + /** + * Returns true if this private file may be added to the associated repository. + * @return true if can add to the repository + */ boolean canAddToRepository() { synchronized (fileSystem) { try { @@ -677,6 +891,11 @@ public class GhidraFileData { } } + /** + * Returns true if this file may be checked-out from the associated repository. + * User's with read-only repository access will not have checkout ability. + * @return true if can checkout + */ boolean canCheckout() { synchronized (fileSystem) { try { @@ -692,6 +911,10 @@ public class GhidraFileData { } } + /** + * Returns true if this file may be checked-in to the associated repository. + * @return true if can check-in + */ boolean canCheckin() { synchronized (fileSystem) { try { @@ -704,6 +927,11 @@ public class GhidraFileData { } } + /** + * Return either the latest version if the file is not checked-out or the version that + * was checked-out or a specific version that was requested. + * @return the version + */ int getVersion() { synchronized (fileSystem) { try { @@ -722,6 +950,10 @@ public class GhidraFileData { } } + /** + * Returns true if this file represents the latest version of the associated domain object. + * @return true if the latest version + */ int getLatestVersion() { synchronized (fileSystem) { if (!isHijacked() && versionedFolderItem != null) { @@ -731,6 +963,10 @@ public class GhidraFileData { } } + /** + * Returns true if this file can be merged with the current versioned file. + * @return true if can merge + */ boolean canMerge() { synchronized (fileSystem) { try { @@ -745,6 +981,13 @@ public class GhidraFileData { } } + /** + * Sets the object to read-only. This method may only be invoked + * for private files (i.e., not versioned). + * @param state if true file will be read-only and may not be updated, if false the + * file may be updated. + * @throws IOException if an IO error occurs. + */ void setReadOnly(boolean state) throws IOException { synchronized (fileSystem) { if (fileSystem.isReadOnly()) { @@ -758,6 +1001,11 @@ public class GhidraFileData { } } + /** + * Returns list of all available versions. + * @return the versions + * @throws IOException if there is an exception getting the history + */ Version[] getVersionHistory() throws IOException { synchronized (fileSystem) { if (versionedFolderItem != null) { @@ -767,6 +1015,16 @@ public class GhidraFileData { } } + /** + * Adds this private file to version control. + * @param comment new version comment + * @param keepCheckedOut if true, the file will be initially checked-out + * @param monitor progress monitor + * @throws FileInUseException if this file is in-use. + * @throws IOException if an IO or access error occurs. Also if file is not + * private. + * @throws CancelledException if the monitor cancelled the operation + */ void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor) throws IOException, CancelledException { DomainObjectAdapter oldDomainObj = null; @@ -852,7 +1110,7 @@ public class GhidraFileData { // TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach - fileManager.clearDomainObject(getPathname()); + projectData.clearDomainObject(getPathname()); oldDomainObj.setDomainFile(new DomainFileProxy("~" + name, oldDomainObj)); oldDomainObj.setTemporary(true); @@ -870,6 +1128,17 @@ public class GhidraFileData { statusChanged(); } + /** + * Checkout this file for update. If this file is already + * private, this method does nothing. + * @param exclusive if true an exclusive checkout will be requested + * @param monitor progress monitor + * @return true if checkout successful, false if an exclusive checkout was not possible + * due to other users having checkouts of this file. A request for a non-exclusive checkout + * will never return false. + * @throws IOException if an IO or access error occurs. + * @throws CancelledException if task monitor cancelled operation. + */ boolean checkout(boolean exclusive, TaskMonitor monitor) throws IOException, CancelledException { if (fileSystem.isReadOnly()) { @@ -1048,6 +1317,18 @@ public class GhidraFileData { } } + /** + * Performs check in to associated repository. File must be checked-out + * and modified since checkout. + * @param checkinHandler provides user input data to complete checkin process. + * @param okToUpgrade if true an upgrade will be performed if needed + * @param monitor the TaskMonitor. + * @throws IOException if an IO or access error occurs + * @throws VersionException if unable to handle domain object version in versioned filesystem. + * If okToUpgrade was false, check exception to see if it can be upgraded + * sometime after doing a checkout. + * @throws CancelledException if task monitor cancelled operation + */ void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor) throws IOException, VersionException, CancelledException { @@ -1074,12 +1355,10 @@ public class GhidraFileData { if (monitor == null) { monitor = TaskMonitor.DUMMY; } - synchronized (fileSystem) { - if (busy) { - throw new FileInUseException(name + " is busy"); - } - busy = true; + if (busy.getAndSet(true)) { + throw new FileInUseException(name + " is busy"); } + projectData.mergeStarted(); try { boolean quickCheckin = ALWAYS_MERGE ? false : quickCheckin(checkinHandler, monitor); @@ -1193,7 +1472,7 @@ public class GhidraFileData { // TODO: Develop way to re-use and re-init domain object instead of a switch-a-roo approach - fileManager.clearDomainObject(getPathname()); + projectData.clearDomainObject(getPathname()); oldDomainObj.setDomainFile(new DomainFileProxy(name, parent.getPathname(), oldDomainObj, -2, fileID, parent.getProjectLocator())); // invalid version (-2) specified to avoid file match @@ -1218,13 +1497,23 @@ public class GhidraFileData { } } finally { - busy = false; - parent.deleteLocalFolderIfEmpty(); - parent.fileChanged(name); + busy.set(false); + try { + parent.deleteLocalFolderIfEmpty(); + parent.fileChanged(name); + } + finally { + projectData.mergeEnded(); + } } } + /** + * Get checkout status associated with a versioned file. + * @return checkout status or null if not checked-out to current associated project. + * @throws IOException if an IO or access error occurs + */ ItemCheckoutStatus getCheckoutStatus() throws IOException { synchronized (fileSystem) { if (!versionedFileSystem.isOnline()) { @@ -1244,6 +1533,11 @@ public class GhidraFileData { } } + /** + * Get a list of checkouts by all users for the associated versioned file. + * @return list of checkouts + * @throws IOException if an IO or access error occurs + */ ItemCheckoutStatus[] getCheckouts() throws IOException { synchronized (fileSystem) { if (!versionedFileSystem.isOnline()) { @@ -1256,6 +1550,13 @@ public class GhidraFileData { } } + /** + * Forcefully terminate a checkout for the associated versioned file. + * The user must be the owner of the checkout or have administrator privilege + * on the versioned filesystem (i.e., repository). + * @param checkoutId checkout ID + * @throws IOException if an IO or access error occurs + */ void terminateCheckout(long checkoutId) throws IOException { synchronized (fileSystem) { if (!versionedFileSystem.isOnline()) { @@ -1268,10 +1569,30 @@ public class GhidraFileData { } } + /** + * Undo "checked-out" file. The original repository file is restored. + * @param keep if true, the private database will be renamed with a .keep + * extension. + * @param inUseOK true if a busy/in-use file state may be ignored, else false + * @throws NotConnectedException if shared project and not connected to repository + * @throws FileInUseException if this file is in-use (when {@code inUseOK} == false). + * @throws IOException if file is not checked-out or an IO / access error occurs. + */ void undoCheckout(boolean keep, boolean inUseOK) throws IOException { undoCheckout(keep, false, inUseOK); } + /** + * Undo "checked-out" file. The original repository file is restored. + * @param keep if true, the private database will be renamed with a .keep + * extension. + * @param force true if operation may be proceed even when not connected to the versioned + * file-system. + * @param inUseOK true if a busy/in-use file state may be ignored, else false + * @throws NotConnectedException if shared project and not connected to repository + * @throws FileInUseException if this file is in-use (when {@code inUseOK} == false). + * @throws IOException if file is not checked-out or an IO / access error occurs. + */ void undoCheckout(boolean keep, boolean force, boolean inUseOK) throws IOException { synchronized (fileSystem) { if (fileSystem.isReadOnly()) { @@ -1369,6 +1690,12 @@ public class GhidraFileData { } } + /** + * Delete the entire database for this file, including any version files. + * @throws FileInUseException if this file is in-use / checked-out. + * @throws UserAccessException if the user does not have permission to delete the file. + * @throws IOException if an IO or access error occurs. + */ void delete() throws IOException { synchronized (fileSystem) { if (fileSystem.isReadOnly()) { @@ -1402,6 +1729,18 @@ public class GhidraFileData { } } + /** + * Deletes a specific version of a file from the versioned filesystem. + * @param version specific version to be deleted. The version must either + * be the oldest or latest, or -1 which will attempt to remove all versions. + * When deleting the latest version, this method could take a long time + * to return since the previous version must be reconstructed within the + * versioned filesystem. + * @throws IOException if an IO error occurs, including the inability + * to delete a version because this item is checked-out, the user does + * not have permission, or the specified version is not the oldest or + * latest. + */ void delete(int version) throws IOException { synchronized (fileSystem) { if (fileSystem.isReadOnly()) { @@ -1432,6 +1771,15 @@ public class GhidraFileData { } } + /** + * Performs merge from current version of versioned file into local checked-out file. + * @param okToUpgrade if true an upgrade will be performed if needed + * @param monitor task monitor + * @throws IOException if an IO or access error occurs + * @throws VersionException if unable to handle domain object version in versioned filesystem. + * If okToUpgrade was false, check exception to see if it can be upgraded + * @throws CancelledException if task monitor cancelled operation + */ void merge(boolean okToUpgrade, TaskMonitor monitor) throws IOException, VersionException, CancelledException { if (fileSystem.isReadOnly()) { @@ -1462,14 +1810,12 @@ public class GhidraFileData { if (monitor == null) { monitor = TaskMonitor.DUMMY; } - synchronized (fileSystem) { - if (busy) { - throw new FileInUseException(name + " is busy"); - } - busy = true; + if (busy.getAndSet(true)) { + throw new FileInUseException(name + " is busy"); } FolderItem tmpItem = null; + projectData.mergeStarted(); try { if (!modifiedSinceCheckout()) { // Quick merge @@ -1566,7 +1912,7 @@ public class GhidraFileData { synchronized (fileSystem) { oldDomainObj = getOpenedDomainObject(); if (oldDomainObj != null) { - fileManager.clearDomainObject(getPathname()); + projectData.clearDomainObject(getPathname()); oldDomainObj.setDomainFile(new DomainFileProxy("~" + name, oldDomainObj)); oldDomainObj.setTemporary(true); } @@ -1580,23 +1926,37 @@ public class GhidraFileData { } } finally { - busy = false; - if (tmpItem != null) { - try { - tmpItem.delete(-1, ClientUtil.getUserName()); + busy.set(false); + try { + if (tmpItem != null) { + try { + tmpItem.delete(-1, ClientUtil.getUserName()); + } + catch (IOException e) { + Msg.error(this, "IO error", e); + } } - catch (IOException e) { - Msg.error(this, "IO error", e); + parent.fileChanged(name); + if (parent.visited()) { + parent.refresh(false, true, null); } } - parent.fileChanged(name); - if (parent.visited()) { - parent.refresh(false, true, null); + finally { + projectData.mergeEnded(); } } } + /** + * Move this file into the newParent folder. + * @param newParent new parent folder within the same project + * @return the newly relocated domain file (the original DomainFile object becomes invalid since it is immutable) + * @throws DuplicateFileException if a file with the same name + * already exists in newParent folder. + * @throws FileInUseException if this file is in-use / checked-out. + * @throws IOException if an IO or access error occurs. + */ GhidraFile moveTo(GhidraFolderData newParent) throws IOException { synchronized (fileSystem) { @@ -1651,6 +2011,18 @@ public class GhidraFileData { } } + /** + * Determine if this file is a link file which corresponds to either a file or folder link. + * The {@link DomainObject} referenced by a link-file may be opened using + * {@link #getReadOnlyDomainObject(Object, int, TaskMonitor)}. The + * {@link #getDomainObject(Object, boolean, boolean, TaskMonitor)} method may also be used + * to obtain a read-only instance. {@link #getImmutableDomainObject(Object, int, TaskMonitor)} + * use is not supported. + * The URL stored within the link-file may be read using {@link #getLinkFileURL()}. + * The content type (see {@link #getContentType()} of a link file will differ from that of the + * linked object (e.g., "LinkedProgram" vs "Program"). + * @return true if link file else false for a normal domain file + */ boolean isLinkFile() { synchronized (fileSystem) { try { @@ -1663,7 +2035,8 @@ public class GhidraFileData { } /** - * Get URL associated with a link-file + * Get URL associated with a link-file. The URL returned may reference either a folder + * or a file within another project/repository. * @return link-file URL or null if not a link-file * @throws IOException if an IO error occurs */ @@ -1675,7 +2048,12 @@ public class GhidraFileData { return LinkHandler.getURL(item); } - public boolean isLinkingSupported() { + /** + * Determine if this file's content type supports linking. + * @return true if linking is supported allowing a link-file to be created which + * references this file, else false. + */ + boolean isLinkingSupported() { synchronized (fileSystem) { try { return getContentHandler().getLinkHandler() != null; @@ -1686,32 +2064,55 @@ public class GhidraFileData { } } - public DomainFile copyToAsLink(GhidraFolderData newParentData) throws IOException { + /** + * Copy this file into the newParent folder as a link file. Restrictions: + *
      + *
    • Specified newParent must reside within a different project since internal linking is + * not currently supported.
    • + *
    • Content type must support linking (see {@link #isLinkingSupported()}).
    • + *
    + * If this file is associated with a temporary transient project (i.e., not a locally + * managed project) the generated link will refer to the remote file with a remote + * Ghidra URL, otherwise a local project storage path will be used. + * @param newParent new parent folder + * @return newly created domain file or null if content type does not support link use. + * @throws IOException if an IO or access error occurs. + */ + DomainFile copyToAsLink(GhidraFolderData newParent) throws IOException { synchronized (fileSystem) { LinkHandler lh = getContentHandler().getLinkHandler(); if (lh == null) { return null; } - return newParentData.copyAsLink(fileManager, getPathname(), name, lh); + return newParent.copyAsLink(projectData, getPathname(), name, lh); } } - GhidraFile copyTo(GhidraFolderData newParentData, TaskMonitor monitor) + /** + * Copy this file into the newParent folder as a private file. + * @param newParent new parent folder + * @param monitor task monitor + * @return newly created domain file + * @throws FileInUseException if this file is in-use / checked-out. + * @throws IOException if an IO or access error occurs. + * @throws CancelledException if task monitor cancelled operation. + */ + GhidraFile copyTo(GhidraFolderData newParent, TaskMonitor monitor) throws IOException, CancelledException { synchronized (fileSystem) { - if (newParentData.getLocalFileSystem().isReadOnly()) { + if (newParent.getLocalFileSystem().isReadOnly()) { throw new ReadOnlyException("copyVersionTo permitted to writeable project only"); } FolderItem item = folderItem != null ? folderItem : versionedFolderItem; - String pathname = newParentData.getPathname(); + String pathname = newParent.getPathname(); String contentType = item.getContentType(); - String targetName = newParentData.getTargetName(name); + String targetName = newParent.getTargetName(name); String user = ClientUtil.getUserName(); try { if (item instanceof DatabaseItem) { BufferFile bufferFile = ((DatabaseItem) item).open(); try { - newParentData.getLocalFileSystem() + newParent.getLocalFileSystem() .createDatabase(pathname, targetName, FileIDFactory.createFileID(), bufferFile, null, contentType, true, monitor, user); } @@ -1722,7 +2123,7 @@ public class GhidraFileData { else if (item instanceof DataFileItem) { InputStream istream = ((DataFileItem) item).getInputStream(); try { - newParentData.getLocalFileSystem() + newParent.getLocalFileSystem() .createDataFile(pathname, targetName, istream, null, contentType, monitor); } @@ -1737,15 +2138,24 @@ public class GhidraFileData { catch (InvalidNameException e) { throw new AssertException("Unexpected error", e); } - newParentData.fileChanged(targetName); - return newParentData.getDomainFile(targetName); + newParent.fileChanged(targetName); + return newParent.getDomainFile(targetName); } } - GhidraFile copyVersionTo(int version, GhidraFolderData destFolderData, TaskMonitor monitor) + /** + * Copy a specific version of this file to the specified destFolder. + * @param version version to copy + * @param destFolder destination parent folder + * @param monitor task monitor + * @return the copied file + * @throws IOException if an IO or access error occurs. + * @throws CancelledException if task monitor cancelled operation. + */ + GhidraFile copyVersionTo(int version, GhidraFolderData destFolder, TaskMonitor monitor) throws IOException, CancelledException { synchronized (fileSystem) { - if (destFolderData.getLocalFileSystem().isReadOnly()) { + if (destFolder.getLocalFileSystem().isReadOnly()) { throw new ReadOnlyException("copyVersionTo permitted to writeable project"); } if (versionedFolderItem == null) { @@ -1754,9 +2164,9 @@ public class GhidraFileData { if (!(versionedFolderItem instanceof DatabaseItem)) { throw new IOException("unsupported operation"); } - String pathname = destFolderData.getPathname(); + String pathname = destFolder.getPathname(); String contentType = versionedFolderItem.getContentType(); - String targetName = destFolderData.getTargetName(name + "_v" + version); + String targetName = destFolder.getTargetName(name + "_v" + version); String user = ClientUtil.getUserName(); try { BufferFile bufferFile = ((DatabaseItem) versionedFolderItem).open(version); @@ -1764,7 +2174,7 @@ public class GhidraFileData { return null; // TODO: not sure this can ever happen - IOException will probably occur instead } try { - destFolderData.getLocalFileSystem() + destFolder.getLocalFileSystem() .createDatabase(pathname, targetName, FileIDFactory.createFileID(), bufferFile, null, contentType, true, monitor, user); } @@ -1775,8 +2185,8 @@ public class GhidraFileData { catch (InvalidNameException e) { throw new AssertException("Unexpected error", e); } - destFolderData.fileChanged(targetName); - return destFolderData.getDomainFile(targetName); + destFolder.fileChanged(targetName); + return destFolder.getDomainFile(targetName); } } @@ -1816,6 +2226,14 @@ public class GhidraFileData { } } + /** + * Pack domain file into specified file. + * Specified file will be overwritten if it already exists. + * @param file destination file + * @param monitor the task monitor + * @throws IOException if there is an exception packing the file + * @throws CancelledException if monitor cancels operation + */ void packFile(File file, TaskMonitor monitor) throws IOException, CancelledException { synchronized (fileSystem) { FolderItem item = folderItem != null ? folderItem : versionedFolderItem; @@ -1823,6 +2241,13 @@ public class GhidraFileData { } } + /** + * Returns the length of this domain file. This size is the minimum disk space + * used for storing this file, but does not account for additional storage space + * used to track changes, etc. + * @return file length + * @throws IOException if IO or access error occurs + */ long length() throws IOException { synchronized (fileSystem) { if (folderItem != null) { @@ -1835,11 +2260,24 @@ public class GhidraFileData { } } + /** + * Returns an ordered map containing the metadata that has been associated with the + * corresponding domain object. The map contains key,value pairs and are ordered by their + * insertion order. + * @return a map containing the metadata that has been associated with the corresponding domain + * object. + */ Map getMetadata() { FolderItem item = (folderItem != null) ? folderItem : versionedFolderItem; return getMetadata(item); } + /** + * Returns an ordered map containing the metadata stored within a specific {@link FolderItem} + * database. The map contains key,value pairs and are ordered by their insertion order. + * @return a map containing the metadata that has been associated with the corresponding domain + * object. Map will be empty for a non-database item. + */ static Map getMetadata(FolderItem item) { GenericDomainObjectDB genericDomainObj = null; try { @@ -1870,14 +2308,14 @@ public class GhidraFileData { @Override public String toString() { - if (fileManager == null) { + if (projectData == null) { return name + "(disposed)"; } - ProjectLocator projectLocator = fileManager.getProjectLocator(); + ProjectLocator projectLocator = projectData.getProjectLocator(); if (projectLocator.isTransient()) { - return fileManager.getProjectLocator().getName() + getPathname(); + return projectData.getProjectLocator().getName() + getPathname(); } - return fileManager.getProjectLocator().getName() + ":" + getPathname(); + return projectData.getProjectLocator().getName() + ":" + getPathname(); } private static class GenericDomainObjectDB extends DomainObjectAdapterDB { @@ -1904,6 +2342,9 @@ public class GhidraFileData { } +/** + * {@link VersionIcon} is the base icon for files which exist within the versioned filesystem. + */ class VersionIcon implements Icon { private static Color VERSION_ICON_COLOR = new GColor("color.bg.icon.versioned"); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolder.java index 360523d0cb..f547eca92b 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolder.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolder.java @@ -20,7 +20,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.List; -import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.model.*; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.store.FileSystem; @@ -32,7 +31,7 @@ import ghidra.util.task.TaskMonitor; public class GhidraFolder implements DomainFolder { - private ProjectFileManager fileManager; + private DefaultProjectData projectData; private LocalFileSystem fileSystem; private FileSystem versionedFileSystem; private DomainFolderChangeListener listener; @@ -40,10 +39,10 @@ public class GhidraFolder implements DomainFolder { private GhidraFolder parent; private String name; - GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) { - this.fileManager = fileManager; - this.fileSystem = fileManager.getLocalFileSystem(); - this.versionedFileSystem = fileManager.getVersionedFileSystem(); + GhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) { + this.projectData = projectData; + this.fileSystem = projectData.getLocalFileSystem(); + this.versionedFileSystem = projectData.getVersionedFileSystem(); this.listener = listener; this.name = FileSystem.SEPARATOR; } @@ -52,7 +51,7 @@ public class GhidraFolder implements DomainFolder { this.parent = parent; this.name = name; - this.fileManager = parent.getProjectFileManager(); + this.projectData = parent.getProjectData(); this.fileSystem = parent.getLocalFileSystem(); this.versionedFileSystem = parent.getVersionedFileSystem(); this.listener = parent.getChangeListener(); @@ -67,17 +66,13 @@ public class GhidraFolder implements DomainFolder { } LocalFileSystem getUserFileSystem() { - return fileManager.getUserFileSystem(); + return projectData.getUserFileSystem(); } DomainFolderChangeListener getChangeListener() { return listener; } - ProjectFileManager getProjectFileManager() { - return fileManager; - } - GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException { GhidraFileData fileData = getFolderData().getFileData(fileName, false); if (fileData == null) { @@ -88,7 +83,7 @@ public class GhidraFolder implements DomainFolder { GhidraFolderData getFolderData() throws FileNotFoundException { if (parent == null) { - return fileManager.getRootFolderData(); + return projectData.getRootFolderData(); } GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false); if (folderData == null) { @@ -106,7 +101,7 @@ public class GhidraFolder implements DomainFolder { private GhidraFolderData createFolderData(String folderName) throws IOException { synchronized (fileSystem) { GhidraFolderData parentData = - parent == null ? fileManager.getRootFolderData() : createFolderData(); + parent == null ? projectData.getRootFolderData() : createFolderData(); GhidraFolderData folderData = parentData.getFolderData(folderName, false); if (folderData == null) { try { @@ -121,7 +116,7 @@ public class GhidraFolder implements DomainFolder { } private GhidraFolderData createFolderData() throws IOException { - GhidraFolderData rootFolderData = fileManager.getRootFolderData(); + GhidraFolderData rootFolderData = projectData.getRootFolderData(); if (parent == null) { return rootFolderData; } @@ -153,12 +148,12 @@ public class GhidraFolder implements DomainFolder { @Override public ProjectLocator getProjectLocator() { - return fileManager.getProjectLocator(); + return projectData.getProjectLocator(); } @Override - public ProjectFileManager getProjectData() { - return fileManager; + public DefaultProjectData getProjectData() { + return projectData; } String getPathname(String childName) { @@ -185,18 +180,9 @@ public class GhidraFolder implements DomainFolder { @Override public URL getSharedProjectURL() { - ProjectLocator projectLocator = getProjectLocator(); - URL projectURL = projectLocator.getURL(); - if (!GhidraURL.isServerRepositoryURL(projectURL)) { - RepositoryAdapter repository = fileManager.getRepository(); - if (repository == null) { - return null; - } - // NOTE: only supports ghidra protocol without extension protocol. - // Assumes any extension protocol use would be reflected in projectLocator URL. - ServerInfo serverInfo = repository.getServerInfo(); - projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(), - repository.getName()); + URL projectURL = projectData.getSharedProjectURL(); + if (projectURL == null) { + return null; } try { // Direct URL construction done so that ghidra protocol extension may be supported @@ -218,7 +204,7 @@ public class GhidraFolder implements DomainFolder { @Override public URL getLocalProjectURL() { - ProjectLocator projectLocator = parent.getProjectLocator(); + ProjectLocator projectLocator = projectData.getProjectLocator(); if (!projectLocator.isTransient()) { return GhidraURL.makeURL(projectLocator, getPathname(), null); } @@ -227,7 +213,7 @@ public class GhidraFolder implements DomainFolder { @Override public boolean isInWritableProject() { - return !getProjectData().getLocalFileSystem().isReadOnly(); + return !fileSystem.isReadOnly(); } @Override @@ -319,7 +305,7 @@ public class GhidraFolder implements DomainFolder { } } catch (IOException e) { - Msg.error(this, "file error for " + parent.getPathname(fileName), e); + Msg.error(this, "file error for " + getPathname(fileName), e); } return null; } @@ -424,7 +410,7 @@ public class GhidraFolder implements DomainFolder { return false; } GhidraFolder other = (GhidraFolder) obj; - if (fileManager != other.fileManager) { + if (projectData != other.projectData) { return false; } return getPathname().equals(other.getPathname()); @@ -437,11 +423,11 @@ public class GhidraFolder implements DomainFolder { @Override public String toString() { - ProjectLocator projectLocator = fileManager.getProjectLocator(); + ProjectLocator projectLocator = projectData.getProjectLocator(); if (projectLocator.isTransient()) { - return fileManager.getProjectLocator().getName() + getPathname(); + return projectData.getProjectLocator().getName() + getPathname(); } - return fileManager.getProjectLocator().getName() + ":" + getPathname(); + return projectData.getProjectLocator().getName() + ":" + getPathname(); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolderData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolderData.java index 8e757cd7d4..6b6b9aac1c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolderData.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFolderData.java @@ -24,14 +24,25 @@ import ghidra.framework.model.*; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.TransientProjectData; import ghidra.framework.store.FileSystem; +import ghidra.framework.store.FolderNotEmptyException; import ghidra.framework.store.local.LocalFileSystem; import ghidra.util.*; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; +/** + * {@link GhidraFolderData} provides the managed object which represents a project folder that + * corresponds to matched folder paths across both a versioned and private + * filesystem and viewed as a single folder at the project level. This class closely mirrors the + * {@link DomainFolder} interface and is used by the {@link GhidraFolder} implementation; both of which + * represent immutable folder references. Changes made to this folder's name or path are not reflected + * in old {@link DomainFolder} instances and must be re-instantiated following such a change. + * Any long-term retention of {@link DomainFolder} and {@link DomainFile} instances requires an + * appropriate change listener to properly discard/reacquire such instances. + */ class GhidraFolderData { - private ProjectFileManager fileManager; + private DefaultProjectData projectData; /** * Folder change listener - change events only sent if folder is visited @@ -59,17 +70,24 @@ class GhidraFolderData { private boolean versionedFolderExists; /** - * General constructor reserved for root folder use only - * @param fileManager - * @param listener + * General constructor reserved for root folder instantiation + * @param projectData associated project data instance + * @param listener folder change listener */ - GhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) { - this.fileManager = fileManager; - this.fileSystem = fileManager.getLocalFileSystem(); - this.versionedFileSystem = fileManager.getVersionedFileSystem(); + GhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) { + this.projectData = projectData; + this.fileSystem = projectData.getLocalFileSystem(); + this.versionedFileSystem = projectData.getVersionedFileSystem(); this.listener = listener; } + /** + * Construct a folder instance with a specified name and a correpsonding parent folder + * @param parent parent folder + * @param name folder name + * @throws FileNotFoundException if folder not found or error occured while checking + * for its existance + */ GhidraFolderData(GhidraFolderData parent, String name) throws FileNotFoundException { if (name == null || name.isEmpty()) { throw new FileNotFoundException("Bad folder name: blank or null"); @@ -77,7 +95,7 @@ class GhidraFolderData { this.parent = parent; this.name = name; - this.fileManager = parent.getProjectFileManager(); + this.projectData = parent.getProjectData(); this.fileSystem = parent.getLocalFileSystem(); this.versionedFileSystem = parent.getVersionedFileSystem(); this.listener = parent.getChangeListener(); @@ -95,36 +113,59 @@ class GhidraFolderData { } /** - * Returns true if folder has complete list of children + * @return true if folder has complete list of children */ boolean visited() { return visited; } + /** + * @return local file system + */ LocalFileSystem getLocalFileSystem() { return fileSystem; } + /** + * @return versioned file system + */ FileSystem getVersionedFileSystem() { return versionedFileSystem; } + /** + * @return local user data file system + */ LocalFileSystem getUserFileSystem() { - return fileManager.getUserFileSystem(); + return projectData.getUserFileSystem(); } + /** + * @return folder change listener + */ DomainFolderChangeListener getChangeListener() { return listener; } - ProjectFileManager getProjectFileManager() { - return fileManager; + /** + * @return project data instance + */ + DefaultProjectData getProjectData() { + return projectData; } + /** + * Get the project locator which identifies the system storage + * are for the local file system and other project related resources. + * @return local project locator + */ ProjectLocator getProjectLocator() { - return fileManager.getProjectLocator(); + return projectData.getProjectLocator(); } + /** + * @return this folder's parent folder or null if this is the root folder. + */ GhidraFolderData getParentData() { return parent; } @@ -143,7 +184,7 @@ class GhidraFolderData { } } else if (folderPath.startsWith(FileSystem.SEPARATOR)) { - return fileManager.getRootFolderData().getFolderPathData(folderPath, lazy); + return projectData.getRootFolderData().getFolderPathData(folderPath, lazy); } if (folderPath.length() == 0) { return this; @@ -168,10 +209,26 @@ class GhidraFolderData { return folderData.getFolderPathData(nextPath, lazy); } + /** + * Return this folder's name. + * @return the name + */ String getName() { return name; } + /** + * Set the name on this domain folder. + * @param newName domain folder name + * @return renamed domain file (the original DomainFolder object becomes invalid since it is + * immutable) + * @throws InvalidNameException if newName contains illegal characters + * @throws DuplicateFileException if a folder named newName + * already exists in this files domain folder. + * @throws FileInUseException if any file within this folder or its descendants is + * in-use / checked-out. + * @throws IOException thrown if an IO or access error occurs. + */ GhidraFolder setName(String newName) throws InvalidNameException, IOException { synchronized (fileSystem) { if (parent == null || fileSystem.isReadOnly()) { @@ -242,6 +299,10 @@ class GhidraFolderData { return path; } + /** + * Returns the full path name to this folder + * @return the path name + */ String getPathname() { if (parent == null) { return FileSystem.SEPARATOR; @@ -254,6 +315,10 @@ class GhidraFolderData { return path; } + /** + * Determine if this folder contains any sub-folders or domain files. + * @return true if this folder is empty. + */ boolean isEmpty() { try { refresh(false, false, null); // visited will be true upon return @@ -266,6 +331,10 @@ class GhidraFolderData { } } + /** + * Get the list of names for all files contained within this folder. + * @return list of file names + */ List getFileNames() { try { refresh(false, false, null); // visited will be true upon return @@ -277,6 +346,10 @@ class GhidraFolderData { return new ArrayList<>(fileList); } + /** + * Get the list of names for all subfolders contained within this folder. + * @return list of file names + */ List getFolderNames() { try { refresh(false, false, null); // visited will be true upon return @@ -289,9 +362,10 @@ class GhidraFolderData { } /** - * Update file list/cache based upon rename of file. - * If this folder has been visited listener will be notified with rename - * @param oldName + * Update file list/cache based upon rename of a file. + * If this folder has been visited the listener will be notified with rename + * @param oldFileName file name prior to rename + * @param newFileName file name after rename */ void fileRenamed(String oldFileName, String newFileName) { GhidraFileData fileData; @@ -314,6 +388,14 @@ class GhidraFolderData { } } + /** + * Update file list/cache based upon change of parent for a file. + * If this folder or the newParent has been visited the listener will be notified with add/move + * details. + * @param newParent new parent folder + * @param oldFileName file name prior to move + * @param newFileName file name after move + */ void fileMoved(GhidraFolderData newParent, String oldFileName, String newFileName) { GhidraFileData fileData; synchronized (fileSystem) { @@ -340,7 +422,7 @@ class GhidraFolderData { * underlying local or versioned file. If this folder has been visited an appropriate * add/remove/change notification will be provided to the listener. * NOTE: Move and Rename situations are not handled - * @param fileName + * @param fileName name of file which has changed */ void fileChanged(String fileName) { synchronized (fileSystem) { @@ -389,7 +471,8 @@ class GhidraFolderData { * visited an appropriate add/remove/change notification will be provided to the listener. * NOTE: Care should be taken using this method as all sub-folder cache data may be disposed! * NOTE: Move and Rename situations are not handled - * @param folderName + * @param folderName name of folder which has changed + * @throws IOException if an IO error occurs during associated refresh */ void folderChanged(String folderName) throws IOException { synchronized (fileSystem) { @@ -404,7 +487,7 @@ class GhidraFolderData { if (folderData.versionedFolderExists || folderData.folderExists) { // preserve subfolder data if (folderData.visited) { - folderData.refresh(true, true, fileManager.getProjectDisposalMonitor()); + folderData.refresh(true, true, projectData.getProjectDisposalMonitor()); } return; } @@ -429,7 +512,7 @@ class GhidraFolderData { /** * Remove and dispose specified subfolder data and notify listener of removal * if this folder has been visited - * @param folderName + * @param folderName name of folder which was removed */ void folderRemoved(String folderName) { synchronized (fileSystem) { @@ -443,6 +526,9 @@ class GhidraFolderData { } } + /** + * Disposes the cached data for this folder and all of its children recursively. + */ void dispose() { visited = false; folderList.clear(); @@ -458,7 +544,7 @@ class GhidraFolderData { // NOTE: clearing the following can cause issues since there may be some residual // activity/use which will get a NPE // parent = null; -// fileManager = null; +// projectData = null; // listener = null; } @@ -476,7 +562,7 @@ class GhidraFolderData { * Refresh set of sub-folder names and identify added/removed folders. * @param recursive recurse into visited subfolders if true * @param monitor recursion task monitor - break from recursion if cancelled - * @throws IOException + * @throws IOException if an IO error occurs during the refresh */ private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException { @@ -653,7 +739,7 @@ class GhidraFolderData { * of visited state, if false refresh is lazy and will not be * performed if a previous refresh set the visited state. * @param monitor recursion task monitor - break from recursion if cancelled - * @throws IOException + * @throws IOException if an IO error occurs during the refresh */ void refresh(boolean recursive, boolean force, TaskMonitor monitor) throws IOException { synchronized (fileSystem) { @@ -699,11 +785,11 @@ class GhidraFolderData { } /** - * Check for existence of subfolder. If this folder visited, rely on folderList - * @param fileName - * @param doRealCheck if true do not rely on fileList - * @return - * @throws IOException + * Check for existence of subfolder. If this folder has previously been visited, + * rely on the cached folderList. + * @param folderName name of folder to look for + * @return true if folder exists, else false + * @throws IOException if an IO error occurs when checking for folder's existance. */ boolean containsFolder(String folderName) throws IOException { synchronized (fileSystem) { @@ -720,8 +806,8 @@ class GhidraFolderData { /** * Create and add new subfolder data object to cache. Data will not be created * if folder does not exist or an IOException occurs. - * @param folderName - * @return folder data or null + * @param folderName name of folder to be added + * @return folder data or null if folder does not exist */ private GhidraFolderData addFolderData(String folderName) { GhidraFolderData folderData = folderDataCache.get(folderName); @@ -739,7 +825,7 @@ class GhidraFolderData { /** * Get folder data for child folder specified by folderName - * @param folderName + * @param folderName name of folder * @param lazy if true folder will not be searched for if not already discovered - in * this case null will be returned * @return folder data or null if not found or lazy=true and not yet discovered @@ -763,10 +849,10 @@ class GhidraFolderData { } /** - * Check for existence of file. If folder visited, rely on fileDataCache - * @param fileName the name of the file to check for - * @return true if this folder contains the fileName - * @throws IOException + * Check for existence of file. If folder previously visited, rely on fileDataCache + * @param fileName the name of the file to look for + * @return true if this folder contains the fileName, else false + * @throws IOException if an IO error occurs while checking for file existance */ public boolean containsFile(String fileName) throws IOException { synchronized (fileSystem) { @@ -783,9 +869,9 @@ class GhidraFolderData { /** * Create and add new file data object to cache. Data will not be created * if file does not exist or an IOException occurs. - * @param fileName - * @return file data or null - * @throws IOException + * @param fileName name of file + * @return file data or null if not found + * @throws IOException if an IO error occurs while checking for file existance */ private GhidraFileData addFileData(String fileName) throws IOException { GhidraFileData fileData = fileDataCache.get(fileName); @@ -793,7 +879,7 @@ class GhidraFolderData { try { fileData = new GhidraFileData(this, fileName); fileDataCache.put(fileName, fileData); - fileManager.updateFileIndex(fileData); + projectData.updateFileIndex(fileData); } catch (FileNotFoundException e) { // ignore @@ -804,10 +890,11 @@ class GhidraFolderData { /** * Get file data for child specified by fileName - * @param fileName + * @param fileName name of file * @param lazy if true file will not be searched for if not already discovered - in * this case null will be returned * @return file data or null if not found or lazy=true and not yet discovered + * @throws IOException if an IO error occurs while checking for file existance */ GhidraFileData getFileData(String fileName, boolean lazy) throws IOException { synchronized (fileSystem) { @@ -822,58 +909,11 @@ class GhidraFolderData { return null; } -// // TODO: Examine! -// private void removeFolderX(String folderName) { -// folderList.remove(folderName); -// folderDataCache.remove(folderName); -// listener.domainFolderRemoved(getDomainFolder(), folderName); -// } -// -// // TODO: Examine! -// void removeFileX(String fileName) { -// fileList.remove(fileName); -// GhidraFileV2Data fileData = fileDataCache.remove(fileName); -// if (fileData != null) { -// fileData.dispose(); -// } -//// TODO: May need to eliminate presence of fileID in callback -// listener.domainFileRemoved(getDomainFolder(), fileName, null /* fileID */); -// } -// -// /** -// * Handle addition of new file. If this folder has been visited, listener -// * will be notified of new file addition or change -// * @param fileName -// * @return -// */ -// // TODO: Examine! -// GhidraFile fileAddedX(String fileName) { -// invalidateFile(fileName); -// GhidraFile df = getDomainFile(fileName); -// if (visited) { -// getFileData(fileName, false); -// if (fileList.add(fileName)) { -// listener.domainFileAdded(df); -// } -// else { -// listenerX.domainFileStatusChanged(df, fileID) -// } -// } -// return df; -// } -// - -// -// // TODO: Examine! -// private GhidraFolder addFolderX(String folderName) { -// invalidateFolder(folderName, false); -// GhidraFolder folder = getDomainFolder(folderName); -// if (folderList.add(folderName) && visited) { -// listener.domainFolderAdded(folder); -// } -// return folder; -// } - + /** + * Get the domain file in this folder with the given fileName. + * @param fileName name of file in this folder to retrieve + * @return domain file or null if there is no file in this folder with the given name. + */ GhidraFile getDomainFile(String fileName) { synchronized (fileSystem) { try { @@ -888,6 +928,11 @@ class GhidraFolderData { return null; } + /** + * Get the domain folder in this folder with the given subfolderName. + * @param subfolderName name of subfolder in this folder to retrieve + * @return domain folder or null if there is no file in this folder with the given name. + */ GhidraFolder getDomainFolder(String subfolderName) { synchronized (fileSystem) { try { @@ -902,10 +947,26 @@ class GhidraFolderData { return null; } + /** + * @return a {@link DomainFolder} instance which corresponds to this folder + */ GhidraFolder getDomainFolder() { return new GhidraFolder(parent.getDomainFolder(), name); } + /** + * Add a domain object to this folder. + * @param fileName domain file name + * @param obj domain object to be stored + * @param monitor progress monitor + * @return domain file created as a result of adding + * the domain object to this folder + * @throws DuplicateFileException thrown if the file name already exists + * @throws InvalidNameException if name is an empty string + * or if it contains characters other than alphanumerics. + * @throws IOException if IO or access error occurs + * @throws CancelledException if the user cancels the create. + */ GhidraFile createFile(String fileName, DomainObject obj, TaskMonitor monitor) throws InvalidNameException, IOException, CancelledException { synchronized (fileSystem) { @@ -937,9 +998,12 @@ class GhidraFolderData { throw new IOException("File creation failed for unknown reason"); } - fileManager.setDomainObject(file.getPathname(), doa); + projectData.setDomainObject(file.getPathname(), doa); doa.setDomainFile(file); doa.setChanged(false); + + projectData.trackDomainFileInUse(doa); + listener.domainFileObjectOpenedForUpdate(file, doa); return file; @@ -950,6 +1014,19 @@ class GhidraFolderData { } } + /** + * Add a new domain file to this folder. + * @param fileName domain file name + * @param packFile packed file containing domain file data + * @param monitor progress monitor + * @return domain file created as a result of adding + * the domain object to this folder + * @throws DuplicateFileException thrown if the file name already exists + * @throws InvalidNameException if name is an empty string + * or if it contains characters other than alphanumerics. + * @throws IOException if IO or access error occurs + * @throws CancelledException if the user cancels the create. + */ GhidraFile createFile(String fileName, File packFile, TaskMonitor monitor) throws InvalidNameException, IOException, CancelledException { synchronized (fileSystem) { @@ -969,6 +1046,15 @@ class GhidraFolderData { } } + /** + * Create a subfolder within this folder. + * @param folderName sub-folder name + * @return the new folder + * @throws DuplicateFileException if a folder by this name already exists + * @throws InvalidNameException if name is an empty string of if it contains characters other + * than alphanumerics. + * @throws IOException if IO or access error occurs + */ GhidraFolderData createFolder(String folderName) throws InvalidNameException, IOException { synchronized (fileSystem) { if (fileSystem.isReadOnly()) { @@ -984,6 +1070,11 @@ class GhidraFolderData { } } + /** + * Deletes this folder, if empty, from the local filesystem + * @throws IOException if IO or access error occurs + * @throws FolderNotEmptyException Thrown if the subfolder is not empty. + */ void delete() throws IOException { synchronized (fileSystem) { if (fileSystem.isReadOnly()) { @@ -1000,6 +1091,9 @@ class GhidraFolderData { } } + /** + * Delete this folder from the local filesystem if empty + */ void deleteLocalFolderIfEmpty() { synchronized (fileSystem) { try { @@ -1018,6 +1112,19 @@ class GhidraFolderData { } } + /** + * Move this folder into the newParent folder. If connected to a repository + * this moves both private and repository folders/files. If not + * connected, only private folders/files are moved. + * @param newParent new parent folder within the same project + * @return the newly relocated folder (the original DomainFolder object becomes invalid since + * it is immutable) + * @throws DuplicateFileException if a folder with the same name + * already exists in newParent folder. + * @throws FileInUseException if this folder or one of its descendants + * contains a file which is in-use / checked-out. + * @throws IOException thrown if an IO or access error occurs. + */ GhidraFolder moveTo(GhidraFolderData newParent) throws IOException { synchronized (fileSystem) { if (newParent.getLocalFileSystem() != fileSystem || fileSystem.isReadOnly()) { @@ -1084,11 +1191,17 @@ class GhidraFolderData { } } + /** + * Determine if the specified folder if an ancestor of this folder + * (i.e., parent, grand-parent, etc.). + * @param folderData folder to be checked + * @return true if the specified folder if an ancestor of this folder + */ boolean isAncestor(GhidraFolderData folderData) { - if (!folderData.fileManager.getProjectLocator().equals(fileManager.getProjectLocator())) { + if (!folderData.projectData.getProjectLocator().equals(projectData.getProjectLocator())) { // check if projects share a common repository - RepositoryAdapter myRepository = fileManager.getRepository(); - RepositoryAdapter otherRepository = folderData.fileManager.getRepository(); + RepositoryAdapter myRepository = projectData.getRepository(); + RepositoryAdapter otherRepository = folderData.projectData.getRepository(); if (myRepository == null || otherRepository == null || !myRepository.getServerInfo().equals(otherRepository.getServerInfo()) || !myRepository.getName().equals(otherRepository.getName())) { @@ -1105,20 +1218,30 @@ class GhidraFolderData { return false; } - GhidraFolder copyTo(GhidraFolderData newParentData, TaskMonitor monitor) + /** + * Copy this folder into the newParent folder. + * @param newParent new parent folder + * @param monitor the task monitor + * @return the new copied folder + * @throws DuplicateFileException if a folder or file by + * this name already exists in the newParent folder + * @throws IOException thrown if an IO or access error occurs. + * @throws CancelledException if task monitor cancelled operation. + */ + GhidraFolder copyTo(GhidraFolderData newParent, TaskMonitor monitor) throws IOException, CancelledException { synchronized (fileSystem) { - if (newParentData.fileSystem.isReadOnly()) { + if (newParent.fileSystem.isReadOnly()) { throw new ReadOnlyException("copyTo permitted to writeable project only"); } - if (isAncestor(newParentData)) { + if (isAncestor(newParent)) { throw new IOException("self-referencing copy not permitted"); } - GhidraFolderData newFolderData = newParentData.getFolderData(name, false); + GhidraFolderData newFolderData = newParent.getFolderData(name, false); if (newFolderData == null) { try { - newFolderData = newParentData.createFolder(name); + newFolderData = newParent.createFolder(name); } catch (InvalidNameException e) { throw new AssertException("Unexpected error", e); @@ -1144,22 +1267,46 @@ class GhidraFolderData { } } - DomainFile copyToAsLink(GhidraFolderData newParentData) throws IOException { + /** + * Create a new link-file in the specified newParent which will reference this folder + * (i.e., linked-folder). Restrictions: + *
      + *
    • Specified newParent must reside within a different project since internal linking is + * not currently supported.
    • + *
    + * If this folder is associated with a temporary transient project (i.e., not a locally + * managed project) the generated link will refer to the remote folder with a remote + * Ghidra URL, otherwise a local project storage path will be used. + * @param newParent new parent folder where link-file is to be created + * @return newly created domain file (i.e., link-file) or null if link use not supported. + * @throws IOException if an IO or access error occurs. + */ + DomainFile copyToAsLink(GhidraFolderData newParent) throws IOException { synchronized (fileSystem) { String linkFilename = name; if (linkFilename == null) { - if (fileManager instanceof TransientProjectData) { - linkFilename = fileManager.getRepository().getName(); + if (projectData instanceof TransientProjectData) { + linkFilename = projectData.getRepository().getName(); } else { - linkFilename = fileManager.getProjectLocator().getName(); + linkFilename = projectData.getProjectLocator().getName(); } } - return newParentData.copyAsLink(fileManager, getPathname(), linkFilename, + return newParent.copyAsLink(projectData, getPathname(), linkFilename, FolderLinkContentHandler.INSTANCE); } } + /** + * Create a link-file within this folder. The link-file may correspond to various types of + * content (e.g., Program, Trace, Folder, etc.) based upon specified link handler. + * @param sourceProjectData referenced content project data within which specified path exists. + * @param pathname path of referenced content with source project data + * @param linkFilename name of link-file to be created within this folder. + * @param lh link file handler used to create specific link file. + * @return link-file + * @throws IOException if IO error occurs during link creation + */ DomainFile copyAsLink(ProjectData sourceProjectData, String pathname, String linkFilename, LinkHandler lh) throws IOException { synchronized (fileSystem) { @@ -1167,7 +1314,7 @@ class GhidraFolderData { throw new ReadOnlyException("copyAsLink permitted to writeable project only"); } - if (sourceProjectData == fileManager) { + if (sourceProjectData == projectData) { // internal linking not yet supported Msg.error(this, "Internal file/folder links not yet supported"); return null; @@ -1177,13 +1324,12 @@ class GhidraFolderData { if (sourceProjectData instanceof TransientProjectData) { RepositoryAdapter repository = sourceProjectData.getRepository(); ServerInfo serverInfo = repository.getServerInfo(); - ghidraUrl = - GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(), - repository.getName(), pathname); + ghidraUrl = GhidraURL.makeURL(serverInfo.getServerName(), + serverInfo.getPortNumber(), repository.getName(), pathname); } else { ProjectLocator projectLocator = sourceProjectData.getProjectLocator(); - if (projectLocator.equals(fileManager.getProjectLocator())) { + if (projectLocator.equals(projectData.getProjectLocator())) { return null; // local internal linking not supported } ghidraUrl = GhidraURL.makeURL(projectLocator, pathname, null); @@ -1216,6 +1362,14 @@ class GhidraFolderData { } } + /** + * Generate a non-conflicting file name for this folder based upon the specified preferred name. + * NOTE: This method is subject to race conditions where returned name could conflict by the + * time it is actually used. + * @param preferredName preferred file name + * @return non-conflicting file name + * @throws IOException if an IO error occurs during file checks + */ String getTargetName(String preferredName) throws IOException { String newName = preferredName; int i = 1; @@ -1242,11 +1396,11 @@ class GhidraFolderData { @Override public String toString() { - ProjectLocator projectLocator = fileManager.getProjectLocator(); + ProjectLocator projectLocator = projectData.getProjectLocator(); if (projectLocator.isTransient()) { - return fileManager.getProjectLocator().getName() + getPathname(); + return projectData.getProjectLocator().getName() + getPathname(); } - return fileManager.getProjectLocator().getName() + ":" + getPathname(); + return projectData.getProjectLocator().getName() + ":" + getPathname(); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolder.java index 78bbf08585..f773fe61a3 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolder.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolder.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +19,8 @@ import ghidra.framework.model.DomainFolderChangeListener; public class RootGhidraFolder extends GhidraFolder { - RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) { - super(fileManager, listener); + RootGhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) { + super(projectData, listener); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolderData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolderData.java index 09659d4324..dab2dafcdc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolderData.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/RootGhidraFolderData.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,18 +20,18 @@ import ghidra.framework.store.FileSystem; public class RootGhidraFolderData extends GhidraFolderData { - RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) { - super(fileManager, listener); + RootGhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) { + super(projectData, listener); } @Override GhidraFolder getDomainFolder() { - return new RootGhidraFolder(getProjectFileManager(), getChangeListener()); + return new RootGhidraFolder(getProjectData(), getChangeListener()); } /** * Provided for testing use only - * @param fs + * @param fs versioned file system */ void setVersionedFileSystem(FileSystem fs) { versionedFileSystem = fs; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java index 9c2eac2aa7..0c75bea23e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFile.java @@ -53,7 +53,7 @@ public interface DomainFile extends Comparable { public final static String READ_ONLY_PROPERTY = "READ_ONLY"; /** - * Get the name of the StoredObj that is associated with the data. + * Get the name of this project file * @return the name */ public String getName(); @@ -83,7 +83,7 @@ public interface DomainFile extends Comparable { public DomainFile setName(String newName) throws InvalidNameException, IOException; /** - * Returns the path name to the domain object. + * Returns the full path name to this file * @return the path name */ public String getPathname(); @@ -114,7 +114,7 @@ public interface DomainFile extends Comparable { public ProjectLocator getProjectLocator(); /** - * Returns content-type string + * Returns content-type string for this file * @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT} * or {@link ContentHandler#UNKNOWN_CONTENT}. */ @@ -134,8 +134,11 @@ public interface DomainFile extends Comparable { /** * Returns changes made to versioned file by others since checkout was performed. + * NOTE: This method is unable to cope with version issues which may require an + * upgrade. * @return change set or null - * @throws VersionException latest version was created with a newer version of software + * @throws VersionException latest version was created with a different version of software + * which prevents rapid determination of change set. * @throws IOException if a folder item access error occurs or change set was * produced by newer version of software and can not be read */ @@ -426,7 +429,7 @@ public interface DomainFile extends Comparable { * @param keep if true, the private database will be renamed with a .keep * extension. * @throws NotConnectedException if shared project and not connected to repository - * @throws FileInUseException if this file is in-use / checked-out. + * @throws FileInUseException if this file is in-use. * @throws IOException if file is not checked-out or an IO / access error occurs. */ public void undoCheckout(boolean keep) throws IOException; @@ -549,7 +552,8 @@ public interface DomainFile extends Comparable { /** * Get the list of consumers (Objects) for this domain file. - * @return empty array list if there are no consumers + * @return true if linking is supported allowing a link-file to be created which + * references this file, else false. */ public List getConsumers(); @@ -593,7 +597,7 @@ public interface DomainFile extends Comparable { /** * Returns the length of this domain file. This size is the minimum disk space * used for storing this file, but does not account for additional storage space - * used to tracks changes, etc. + * used to track changes, etc. * @return file length * @throws IOException if IO or access error occurs */ diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolder.java index 583e8ad5e8..c68de7fe3e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolder.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainFolder.java @@ -83,7 +83,7 @@ public interface DomainFolder extends Comparable { public ProjectData getProjectData(); /** - * Returns the path name to the domain object. + * Returns the full path name to this folder * @return the path name */ public String getPathname(); @@ -183,11 +183,10 @@ public interface DomainFolder extends Comparable { throws InvalidNameException, IOException, CancelledException; /** - * Create a subfolder of this folder. + * Create a subfolder within this folder. * @param folderName sub-folder name - * @return the folder - * @throws DuplicateFileException if a folder by - * this name already exists + * @return the new folder + * @throws DuplicateFileException if a folder by this name already exists * @throws InvalidNameException if name is an empty string of if it contains characters other * than alphanumerics. * @throws IOException if IO or access error occurs @@ -195,16 +194,16 @@ public interface DomainFolder extends Comparable { public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException; /** - * Deletes this folder and all of its contents + * Deletes this folder, if empty, from the local filesystem * @throws IOException if IO or access error occurs - * @throws FolderNotEmptyException Thrown if the subfolder is not empty. + * @throws FolderNotEmptyException Thrown if this folder is not empty. */ public void delete() throws IOException; /** - * Move this folder into the newParent folder. If connected to an archive - * this affects both private and repository folders and files. If not - * connected, only private folders and files are affected. + * Move this folder into the newParent folder. If connected to a repository + * this moves both private and repository folders/files. If not + * connected, only private folders/files are moved. * @param newParent new parent folder within the same project * @return the newly relocated folder (the original DomainFolder object becomes invalid since * it is immutable) @@ -220,7 +219,7 @@ public interface DomainFolder extends Comparable { * Copy this folder into the newParent folder. * @param newParent new parent folder * @param monitor the task monitor - * @return the copied folder + * @return the new copied folder * @throws DuplicateFileException if a folder or file by * this name already exists in the newParent folder * @throws IOException thrown if an IO or access error occurs. @@ -230,16 +229,17 @@ public interface DomainFolder extends Comparable { throws IOException, CancelledException; /** - * Copy this folder into the newParent folder as a link file. Restrictions: + * Create a new link-file in the specified newParent which will reference this folder + * (i.e., linked-folder). Restrictions: *
      *
    • Specified newParent must reside within a different project since internal linking is * not currently supported.
    • *
    * If this folder is associated with a temporary transient project (i.e., not a locally - * managed project) the generated link will refer to the remote file with a remote + * managed project) the generated link will refer to the remote folder with a remote * Ghidra URL, otherwise a local project storage path will be used. - * @param newParent new parent folder - * @return newly created domain file or null if link use not supported. + * @param newParent new parent folder where link-file is to be created + * @return newly created domain file (i.e., link-file) or null if link use not supported. * @throws IOException if an IO or access error occurs. */ public DomainFile copyToAsLink(DomainFolder newParent) throws IOException; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectClosedListener.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectClosedListener.java index 075b24ecdf..cfb31a187f 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectClosedListener.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/DomainObjectClosedListener.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,5 +19,10 @@ package ghidra.framework.model; * An interface that allows for a callback when a {@link DomainObject} is closed. */ public interface DomainObjectClosedListener { - public void domainObjectClosed(); + + /** + * Callback indicating that the specified {@link DomainObject} has been closed. + * @param dobj domain object + */ + public void domainObjectClosed(DomainObject dobj); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectData.java index fb635f8739..e88f20f7dc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectData.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/ProjectData.java @@ -191,8 +191,10 @@ public interface ProjectData { TaskMonitor monitor) throws IOException, CancelledException; /** - * Close the project storage associated with this project data object. - * NOTE: This should not be invoked if this object is utilized by a Project instance. + * Initiate disposal of this project data object. Any files already open will delay + * disposal until they are closed. + * NOTE: This should only be invoked by the controlling object which created/opened this + * instance to avoid premature disposal. */ public void close(); @@ -209,4 +211,18 @@ public interface ProjectData { */ public void testValidName(String name, boolean isPath) throws InvalidNameException; + /** + * Generate a repository URL which corresponds to this project data if applicable. + * Local private projects will return null; + * @return repository URL which corresponds to this project data or null if not applicable. + */ + public URL getSharedProjectURL(); + + /** + * Generate a local URL which corresponds to this project data if applicable. + * Remote transient project data will return null; + * @return local URL which corresponds to this project data or null if not applicable. + */ + public URL getLocalProjectURL(); + } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java index f576be32b1..0553bf530e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java @@ -25,7 +25,7 @@ import org.jdom.input.SAXBuilder; import org.jdom.output.XMLOutputter; import ghidra.framework.client.RepositoryAdapter; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.data.TransientDataManager; import ghidra.framework.model.*; import ghidra.framework.options.SaveState; @@ -58,7 +58,7 @@ public class DefaultProject implements Project { private DefaultProjectManager projectManager; private ProjectLocator projectLocator; - private ProjectFileManager fileMgr; + private DefaultProjectData projectData; private ToolManagerImpl toolManager; private boolean changed; // flag for whether the project configuration has changed @@ -66,7 +66,7 @@ public class DefaultProject implements Project { private Map dataMap = new HashMap<>(); private Map projectConfigMap = new HashMap<>(); - private Map otherViews = new HashMap<>(); + private Map otherViewsMap = new HashMap<>(); private Set visibleViews = new HashSet<>(); private WeakSet viewListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet(); @@ -86,21 +86,10 @@ public class DefaultProject implements Project { this.projectManager = projectManager; this.projectLocator = projectLocator; - boolean success = false; - try { - Msg.info(this, "Creating project: " + projectLocator.toString()); - fileMgr = new ProjectFileManager(projectLocator, repository, true); - if (!SystemUtilities.isInHeadlessMode()) { - toolManager = new ToolManagerImpl(this); - } - success = true; - } - finally { - if (!success) { - if (fileMgr != null) { - fileMgr.dispose(); - } - } + Msg.info(this, "Creating project: " + projectLocator.toString()); + projectData = new DefaultProjectData(projectLocator, repository, true); + if (!SystemUtilities.isInHeadlessMode()) { + toolManager = new ToolManagerImpl(this); } initializeNewProject(); } @@ -122,28 +111,18 @@ public class DefaultProject implements Project { this.projectManager = projectManager; this.projectLocator = projectLocator; - boolean success = false; - try { - Msg.info(this, "Opening project: " + projectLocator.toString()); - fileMgr = new ProjectFileManager(projectLocator, true, resetOwner); - if (!SystemUtilities.isInHeadlessMode()) { - toolManager = new ToolManagerImpl(this); - } - success = true; - } - finally { - if (!success) { - if (fileMgr != null) { - fileMgr.dispose(); - } - } + Msg.info(this, "Opening project: " + projectLocator.toString()); + projectData = new DefaultProjectData(projectLocator, true, resetOwner); + if (!SystemUtilities.isInHeadlessMode()) { + toolManager = new ToolManagerImpl(this); } } /** * Constructor for opening a URL-based project * - * @param connection project connection + * @param projectManager the manager of this project + * @param connection project URL connection (not previously used) * @throws IOException if I/O error occurs. */ protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection) @@ -151,27 +130,17 @@ public class DefaultProject implements Project { this.projectManager = projectManager; - boolean success = false; - try { - Msg.info(this, "Opening project/repository: " + connection.getURL()); - fileMgr = (ProjectFileManager) connection.getProjectData(); - if (fileMgr == null) { - throw new IOException("Failed to open project/repository: " + connection.getURL()); - } + Msg.info(this, "Opening project/repository: " + connection.getURL()); + projectData = (DefaultProjectData) connection.getProjectData(); + if (projectData == null) { + throw new IOException("Failed to open project/repository: " + connection.getURL()); + } - projectLocator = fileMgr.getProjectLocator(); - if (!SystemUtilities.isInHeadlessMode()) { - toolManager = new ToolManagerImpl(this); - } - success = true; - } - finally { - if (!success) { - if (fileMgr != null) { - fileMgr.dispose(); - } - } + projectLocator = projectData.getProjectLocator(); + if (!SystemUtilities.isInHeadlessMode()) { + toolManager = new ToolManagerImpl(this); } + initializeNewProject(); } @@ -300,20 +269,20 @@ public class DefaultProject implements Project { return null; } - ProjectFileManager projectData = (ProjectFileManager) c.getProjectData(); + DefaultProjectData projectData = (DefaultProjectData) c.getProjectData(); if (projectData == null) { throw new IOException( "Failed to view specified project/repository: " + GhidraURL.getDisplayString(url)); } url = projectData.getProjectLocator().getURL(); // transform to repository root URL - otherViews.put(url, projectData); + otherViewsMap.put(url, projectData); return projectData; } @Override public ProjectData addProjectView(URL url, boolean visible) throws IOException { - synchronized (otherViews) { + synchronized (otherViewsMap) { if (isClosed) { throw new IOException("project is closed"); } @@ -322,7 +291,7 @@ public class DefaultProject implements Project { throw new IOException("Invalid Ghidra URL specified: " + url); } - ProjectData projectData = otherViews.get(url); + ProjectData projectData = otherViewsMap.get(url); if (projectData == null) { projectData = openProjectView(url); } @@ -340,13 +309,13 @@ public class DefaultProject implements Project { */ @Override public void removeProjectView(URL url) { - synchronized (otherViews) { - ProjectFileManager dataMgr = otherViews.remove(url); + synchronized (otherViewsMap) { + DefaultProjectData dataMgr = otherViewsMap.remove(url); if (dataMgr != null) { if (visibleViews.remove(url)) { notifyVisibleViewRemoved(url); } - dataMgr.dispose(); + dataMgr.close(); Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url)); changed = true; } @@ -401,22 +370,20 @@ public class DefaultProject implements Project { @Override public RepositoryAdapter getRepository() { - return fileMgr.getRepository(); + return projectData.getRepository(); } @Override public void close() { - synchronized (otherViews) { + synchronized (otherViewsMap) { isClosed = true; - Iterator iter = otherViews.values().iterator(); - while (iter.hasNext()) { - ProjectFileManager dataMgr = iter.next(); + for (DefaultProjectData dataMgr : otherViewsMap.values()) { if (dataMgr != null) { - dataMgr.dispose(); + dataMgr.close(); } } - otherViews.clear(); + otherViewsMap.clear(); } try { @@ -429,7 +396,7 @@ public class DefaultProject implements Project { } } finally { - fileMgr.dispose(); + projectData.close(); } } @@ -449,7 +416,7 @@ public class DefaultProject implements Project { @Override public void restore() { // if there is a saved project, restore it - File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE); + File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE); String errorMsg = null; Throwable error = null; try { @@ -593,7 +560,7 @@ public class DefaultProject implements Project { try { // save tool state root.addContent(toolManager.saveToXml()); // the tool manager will save the open tools' state - File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE); + File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE); OutputStream os = new FileOutputStream(saveFile); Document doc = new Document(root); XMLOutputter xmlOut = new GenericXMLOutputter(); @@ -629,15 +596,14 @@ public class DefaultProject implements Project { @Override public List getOpenData() { ArrayList openFiles = new ArrayList<>(); - fileMgr.findOpenFiles(openFiles); + projectData.findOpenFiles(openFiles); ProjectData[] viewedProjs = getViewedProjectData(); for (ProjectData viewedProj : viewedProjs) { - ((ProjectFileManager) viewedProj).findOpenFiles(openFiles); + ((DefaultProjectData) viewedProj).findOpenFiles(openFiles); } List list = new ArrayList<>(); TransientDataManager.getTransients(list); - for (int i = 0; i < list.size(); i++) { - DomainFile df = list.get(i); + for (DomainFile df : list) { if (df != null && df.isOpen()) { openFiles.add(df); } @@ -646,8 +612,8 @@ public class DefaultProject implements Project { } @Override - public ProjectFileManager getProjectData() { - return fileMgr; + public DefaultProjectData getProjectData() { + return projectData; } @Override @@ -662,12 +628,12 @@ public class DefaultProject implements Project { @Override public ProjectData getProjectData(ProjectLocator locator) { - if (locator.equals(fileMgr.getProjectLocator())) { - return fileMgr; + if (locator.equals(projectData.getProjectLocator())) { + return projectData; } - synchronized (otherViews) { - for (ProjectData data : otherViews.values()) { + synchronized (otherViewsMap) { + for (ProjectData data : otherViewsMap.values()) { if (locator.equals(data.getProjectLocator())) { return data; } @@ -680,30 +646,30 @@ public class DefaultProject implements Project { @Override public ProjectData getProjectData(URL url) { if (projectLocator.getURL().equals(url)) { - return fileMgr; + return projectData; } URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL(); if (remoteURL != null) { remoteURL = GhidraURL.getProjectURL(url); } if (remoteURL.equals(url)) { - return fileMgr; + return projectData; } - synchronized (otherViews) { - return otherViews.get(url); + synchronized (otherViewsMap) { + return otherViewsMap.get(url); } } @Override public ProjectData[] getViewedProjectData() { - synchronized (otherViews) { + synchronized (otherViewsMap) { // only return visible viewed project List list = new ArrayList<>(); - for (URL url : otherViews.keySet()) { + for (URL url : otherViewsMap.keySet()) { if (visibleViews.contains(url)) { - list.add(otherViews.get(url)); + list.add(otherViewsMap.get(url)); } } @@ -715,11 +681,9 @@ public class DefaultProject implements Project { @Override public void releaseFiles(Object consumer) { - fileMgr.releaseDomainFiles(consumer); - synchronized (otherViews) { - Iterator it = otherViews.values().iterator(); - while (it.hasNext()) { - ProjectFileManager mgr = it.next(); + projectData.releaseDomainFiles(consumer); + synchronized (otherViewsMap) { + for (DefaultProjectData mgr : otherViewsMap.values()) { mgr.releaseDomainFiles(consumer); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java index 456329843e..c0350f7bd8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java @@ -78,6 +78,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector { } catch (RepositoryNotFoundException e) { statusCode = StatusCode.NOT_FOUND; + return statusCode; } } else if (!repositoryServerAdapter.isCancelled()) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultLocalGhidraProtocolConnector.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultLocalGhidraProtocolConnector.java index 770a6117aa..cbd62b6290 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultLocalGhidraProtocolConnector.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultLocalGhidraProtocolConnector.java @@ -21,7 +21,7 @@ import java.net.URL; import ghidra.framework.client.NotConnectedException; import ghidra.framework.client.RepositoryAdapter; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.ProjectLocator; import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode; import ghidra.framework.store.LockException; @@ -126,13 +126,13 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector * @return project data instance or null if project not found * @throws IOException if IO error occurs */ - ProjectFileManager getLocalProjectData(boolean readOnlyAccess) throws IOException { + DefaultProjectData getLocalProjectData(boolean readOnlyAccess) throws IOException { if (connect(readOnlyAccess) != StatusCode.OK) { return null; } try { - return new ProjectFileManager(localStorageLocator, !readOnlyAccess, false); + return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false); } catch (NotOwnerException | ReadOnlyException e) { statusCode = StatusCode.UNAUTHORIZED; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java index e3e75b906c..447b97bea3 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java @@ -19,7 +19,7 @@ import java.io.*; import java.net.*; import ghidra.framework.client.*; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.ProjectData; import ghidra.util.exception.AssertException; @@ -69,38 +69,6 @@ public class GhidraURLConnection extends URLConnection { } } - // TODO: consider implementing request and response headers - -// /** -// * Ghidra Status-Code 200: OK. -// */ -// public static final int GHIDRA_OK = 200; -// -// /** -// * Ghidra Status-Code 401: Unauthorized. -// * This response code includes a variety of connection errors -// * which are reported/logged by the Ghidra Server support code. -// */ -// public static final int GHIDRA_UNAUTHORIZED = 401; -// -// /** -// * Ghidra Status-Code 404: Not Found. -// */ -// public static final int GHIDRA_NOT_FOUND = 404; -// -// /** -// * Ghidra Status-Code 423: Locked -// * Caused by attempt to open local project data with write-access when project is -// * already opened and locked. -// */ -// public static final int GHIDRA_LOCKED = 423; -// -// /** -// * Ghidra Status-Code 503: Unavailable -// * Caused by other connection failure -// */ -// public static final int GHIDRA_UNAVAILABLE = 503; - /** * Ghidra content type - domain folder/file wrapped within GhidraURLWrappedContent object. * @see GhidraURLWrappedContent @@ -117,7 +85,7 @@ public class GhidraURLConnection extends URLConnection { private GhidraProtocolConnector protocolConnector; - private ProjectFileManager projectData; + private DefaultProjectData projectData; private Object refObject; private boolean readOnly = true; @@ -270,12 +238,17 @@ public class GhidraURLConnection extends URLConnection { } /** - * If URL connects and corresponds to a valid repository, this method + * If URL connects and corresponds to a valid repository or local project, this method * may be used to obtain the associated ProjectData object. The caller is - * responsible for closing the returned project data when no longer in-use, - * failure to do so may prevent release of repository handle to server. - * Only a single call to this method is permitted. - * @return transient project data or null if unavailable + * responsible for properly {@link ProjectData#close() closing} the returned project data + * instance when no longer in-use, failure to do so may prevent release of repository handle + * to server until process exits. It is important that {@link ProjectData#close()} is + * invoked once, and only once, per call to this method to ensure project "use" tracking + * is properly maintained. Improperly invoking the close method on a shared transient + * {@link ProjectData} instance may cause the underlying storage to be prematurely + * disposed. + * + * @return project data which corresponds to this connection or null if unavailable * @throws IOException if an IO error occurs */ public ProjectData getProjectData() throws IOException { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLWrappedContent.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLWrappedContent.java index 8e919c29a5..09d41103b0 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLWrappedContent.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLWrappedContent.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.*; import ghidra.util.InvalidNameException; @@ -43,7 +44,7 @@ public class GhidraURLWrappedContent { private List consumers = new ArrayList(); - private ProjectData projectData; + private DefaultProjectData projectData; private Object refObject; public GhidraURLWrappedContent(GhidraURLConnection c) { @@ -82,6 +83,8 @@ public class GhidraURLWrappedContent { /** * Close associated {@link ProjectData} when all consumers have released wrapped object. + * Underlying project data instance may remain active until all open project files have been + * released/closed. */ private void closeProjectData() { if (projectData != null) { @@ -91,8 +94,8 @@ public class GhidraURLWrappedContent { refObject = null; } - private DomainFolder getExplicitFolder(String folderPath) throws InvalidNameException, - IOException { + private DomainFolder getExplicitFolder(String folderPath) + throws InvalidNameException, IOException { DomainFolder folder = projectData.getRootFolder(); for (String name : folderPath.substring(1).split("/")) { DomainFolder subfolder = folder.getFolder(name); @@ -110,7 +113,7 @@ public class GhidraURLWrappedContent { return; } - projectData = c.getProjectData(); + projectData = (DefaultProjectData) c.getProjectData(); String folderItemName = c.getFolderItemName(); String folderPath = c.getFolderPath(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectData.java index 411bf33734..7be8605434 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectData.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectData.java @@ -16,10 +16,11 @@ package ghidra.framework.protocol.ghidra; import java.io.IOException; +import java.net.URL; import generic.timer.GhidraSwinglessTimer; import ghidra.framework.client.RepositoryAdapter; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.ProjectLocator; import ghidra.framework.remote.RepositoryHandle; import ghidra.framework.store.LockException; @@ -27,7 +28,7 @@ import ghidra.util.Msg; import ghidra.util.SystemUtilities; import utilities.util.FileUtilities; -public class TransientProjectData extends ProjectFileManager { +public class TransientProjectData extends DefaultProjectData { private TransientProjectManager dataMgr; final RepositoryInfo repositoryInfo; @@ -146,23 +147,30 @@ public class TransientProjectData extends ProjectFileManager { } stopCleanupTimer(); disposed = true; - } - Msg.debug(this, "Removing transient project (" + repositoryInfo.toShortString() + "): " + - getProjectLocator().getProjectDir()); + String msgTail = " transient project (" + repositoryInfo.toShortString() + "): " + + getProjectLocator().getProjectDir() + ", URL: " + repositoryInfo.getURL(); + if (instanceUseCount != 0) { + Msg.error(this, "Premature removal of active" + msgTail); + } + else { + Msg.debug(this, "Removing idle" + msgTail); + } + } dataMgr.cleanupProjectData(repositoryInfo, this); super.dispose(); // disconnects repository - // TODO: There could still be open files if they have not been properly released/closed !! + // Remove temporary project storage + // NOTE: This could be affected by project files which still remain open ProjectLocator locator = getProjectLocator(); FileUtilities.deleteDir(locator.getProjectDir()); locator.getMarkerFile().delete(); } @Override - public void dispose() { + public void close() { // prevent normal disposal - rely on finalizer and shutdown hook synchronized (cleanupTimer) { if (instanceUseCount == 0) { @@ -178,13 +186,18 @@ public class TransientProjectData extends ProjectFileManager { } @Override - protected void finalize() throws Throwable { - try { - forcedDispose(); - } - catch (Throwable t) { - // ignore errors during finalize - } - super.finalize(); + protected void dispose() { + // rely on forcedDispose to invoke super.dispose() } + + @Override + public URL getSharedProjectURL() { + return repositoryInfo.getURL(); + } + + @Override + public URL getLocalProjectURL() { + return null; + } + } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java index fbd2a35c3c..3ccb505b01 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/TransientProjectManager.java @@ -71,8 +71,9 @@ public class TransientProjectManager { } private TransientProjectManager() { - Runtime.getRuntime().addShutdownHook( - new Thread((Runnable) () -> dispose(), "TransientProjectManager Shutdown Hook")); + Runtime.getRuntime() + .addShutdownHook(new Thread((Runnable) () -> dispose(), + "TransientProjectManager Shutdown Hook")); } /** @@ -80,8 +81,6 @@ public class TransientProjectManager { * connections. WARNING: This method intended for testing only. */ public synchronized void dispose() { - // TODO: server handles may be shared with non-transient projects - TransientProjectData[] projectDataArray = repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]); for (TransientProjectData projectData : projectDataArray) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/task/GTaskManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/task/GTaskManager.java index ab10f97690..a3de7e9036 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/task/GTaskManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/task/GTaskManager.java @@ -15,17 +15,16 @@ */ package ghidra.framework.task; -import generic.concurrent.GThreadPool; -import ghidra.framework.model.DomainObjectClosedListener; -import ghidra.framework.model.UndoableDomainObject; -import ghidra.util.Msg; -import ghidra.util.exception.CancelledException; - import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; +import generic.concurrent.GThreadPool; +import ghidra.framework.model.*; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; + /** * Class for managing a queue of tasks to be executed, one at a time, in priority order. All the * tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject @@ -95,7 +94,8 @@ public class GTaskManager { domainObject.addCloseListener(new DomainObjectClosedListener() { @Override - public void domainObjectClosed() { + public void domainObjectClosed(DomainObject dobj) { + // assert dobj == domainObj GTaskManagerFactory.domainObjectClosed(domainObject); domainObject = null; } @@ -107,7 +107,7 @@ public class GTaskManager { * * @param task the task to be run. * @param priority the priority of the task. Lower numbers are run before higher numbers. - * @param useCurrentGroup. If true, this task will be rolled into the current transaction group + * @param useCurrentGroup If true, this task will be rolled into the current transaction group * if one exists. If false, any open transaction * will be closed and a new transaction will be opened before * this task is run. @@ -680,7 +680,8 @@ public class GTaskManager { taskListener.taskCompleted(task, result); } catch (Throwable unexpected) { - Msg.error(this, "Unexpected exception notifying listener of task completed", unexpected); + Msg.error(this, "Unexpected exception notifying listener of task completed", + unexpected); } } @@ -705,7 +706,8 @@ public class GTaskManager { taskListener.taskScheduled(scheduledTask); } catch (Throwable unexpected) { - Msg.error(this, "Unexpected exception notifying listener of task scheduled", unexpected); + Msg.error(this, "Unexpected exception notifying listener of task scheduled", + unexpected); } } diff --git a/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyProjectData.java b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyProjectData.java index ca01d34d90..648dcb920b 100644 --- a/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyProjectData.java +++ b/Ghidra/Framework/Project/src/test/java/ghidra/framework/model/TestDummyProjectData.java @@ -16,6 +16,7 @@ package ghidra.framework.model; import java.io.IOException; +import java.net.URL; import java.util.List; import ghidra.framework.client.RepositoryAdapter; @@ -95,6 +96,18 @@ public class TestDummyProjectData implements ProjectData { return null; } + @Override + public URL getSharedProjectURL() { + // stub + return null; + } + + @Override + public URL getLocalProjectURL() { + // stub + return null; + } + @Override public void addDomainFolderChangeListener(DomainFolderChangeListener listener) { // stub diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java index 4840a6812d..cb90856d3e 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FrontEndPluginScreenShots.java @@ -36,7 +36,7 @@ import docking.wizard.WizardPanel; import generic.theme.GThemeDefaults.Colors; import ghidra.app.plugin.core.archive.RestoreDialog; import ghidra.framework.data.GhidraFileData; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.main.*; import ghidra.framework.model.*; import ghidra.framework.plugintool.dialog.*; @@ -694,7 +694,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator { Project project = env.getProject(); program = env.getProgram("WinHelloCPP.exe"); - ProjectFileManager projectData = (ProjectFileManager) project.getProjectData(); + DefaultProjectData projectData = (DefaultProjectData) project.getProjectData(); projectData.getRootFolder().createFile("HelloCpp.exe", program, TaskMonitor.DUMMY); // Create other project to be viewed diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/main/ProjectInfoFilesystemTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/main/ProjectInfoFilesystemTest.java index cea1f05372..7a71155a59 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/main/ProjectInfoFilesystemTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/framework/main/ProjectInfoFilesystemTest.java @@ -26,7 +26,7 @@ import org.junit.*; import docking.action.DockingActionIf; import generic.test.TestUtils; -import ghidra.framework.data.ProjectFileManager; +import ghidra.framework.data.DefaultProjectData; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.store.ItemCheckoutStatus; @@ -276,7 +276,7 @@ public class ProjectInfoFilesystemTest extends AbstractGhidraHeadedIntegrationTe } private void checkProjectInfo(Class filesystemClass, String storageType) { - ProjectFileManager projectData = (ProjectFileManager) project.getProjectData(); + DefaultProjectData projectData = (DefaultProjectData) project.getProjectData(); String msg = "Expected " + filesystemClass.getSimpleName() + ": "; assertTrue(msg + "Local FileSystem", filesystemClass.isInstance( TestUtils.invokeInstanceMethod("getLocalFileSystem", projectData))); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java index 0100841dc3..3aa9aa6b7f 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java @@ -802,9 +802,9 @@ public class ServerTestUtil { public static void createRepositoryItem(LocalFileSystem repoFilesystem, String name, String folderPath, Program program) throws Exception { - ContentHandler contentHandler = DomainObjectAdapter.getContentHandler(program); - long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name, - program, TaskMonitor.DUMMY); + ContentHandler contentHandler = DomainObjectAdapter.getContentHandler(program); + long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name, program, + TaskMonitor.DUMMY); LocalFolderItem item = repoFilesystem.getItem(folderPath, name); if (item == null) { throw new IOException("Item not found: " + FileSystem.SEPARATOR + name);