GP-3551 Added support for internal project link-files with improved link

support within project data tree. Linked-folders are now supported.
Addressed link-support issues related to various actions.  Revised
link-file storage to use smaller non-DB storage. This change does impact
the Ghidra Server.
This commit is contained in:
ghidra1
2025-04-25 15:44:03 -04:00
parent 7482131bcc
commit 1aa7b089c0
209 changed files with 10096 additions and 3050 deletions
@@ -15,6 +15,7 @@
*/
package help.screenshot;
import java.awt.Dialog;
import java.io.IOException;
import javax.swing.JComboBox;
@@ -27,6 +28,7 @@ import ghidra.app.util.exporter.Exporter;
import ghidra.framework.model.*;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.listing.Program;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@@ -40,10 +42,15 @@ public class ExporterPluginScreenShots extends GhidraScreenShotGenerator {
Preferences.setProperty(Preferences.LAST_EXPORT_DIRECTORY, "/path");
DomainFile df = createDomainFile();
ExporterDialog dialog = new ExporterDialog(tool, df);
runSwing(() -> tool.showDialog(dialog), false);
runSwing(() -> ExporterDialog.show(tool, df), false);
Dialog dialog = waitForJDialog("Export Program_A");
waitForSwing();
captureDialog(dialog);
Swing.runNow(() -> dialog.dispose());
}
@Test
@@ -37,8 +37,7 @@ import docking.wizard.WizardDialog;
import generic.theme.GThemeDefaults.Colors;
import ghidra.app.plugin.core.archive.RestoreDialog;
import ghidra.framework.Application;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.data.GhidraFileData;
import ghidra.framework.data.*;
import ghidra.framework.main.*;
import ghidra.framework.main.wizard.project.*;
import ghidra.framework.model.*;
@@ -56,10 +55,14 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.extensions.ExtensionDetails;
import ghidra.util.task.TaskMonitor;
import resources.MultiIcon;
import resources.icons.TranslateIcon;
public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
private static final String RIGHT_ARROW = "\u2b95";
private static final String OTHER_PROJECT = "Other_Project";
Icon icon = (Icon) getInstanceField("CONVERT_ICON", ProjectChooseRepositoryWizardModel.class);
private Icon icon =
(Icon) getInstanceField("CONVERT_ICON", ProjectChooseRepositoryWizardModel.class);
public FrontEndPluginScreenShots() {
super();
@@ -139,8 +142,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
TestDummyWizardModel<ProjectWizardData> panelMgr =
new TestDummyWizardModel<ProjectWizardData>(panel, false, true, false,
"Change Shared Project Information", 600, 375,
new ProjectWizardData(), icon);
"Change Shared Project Information", 600, 375, new ProjectWizardData(), icon);
WizardDialog wizard = new WizardDialog(panelMgr, false);
wizard.show();
@@ -156,9 +158,8 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
ProjectWizardData data = new ProjectWizardData();
data.setServerInfo(new ServerInfo("server1", 13100));
TestDummyWizardModel<ProjectWizardData> panelMgr =
new TestDummyWizardModel<>(panel, false, true, false,
"Change Shared Project Information", 600, 180, data, icon);
TestDummyWizardModel<ProjectWizardData> panelMgr = new TestDummyWizardModel<>(panel, false,
true, false, "Change Shared Project Information", 600, 180, data, icon);
WizardDialog wizard = new WizardDialog(panelMgr, false);
wizard.show();
@@ -326,6 +327,40 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
captureIconAndText(multiIcon, "Example");
}
@Test
public void testAbsoluteFileLinkIcon() {
Icon programIcon = ProgramContentHandler.PROGRAM_ICON;
MultiIcon multiIcon = new MultiIcon(programIcon);
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
captureIconAndText(multiIcon, "Example " + RIGHT_ARROW + " /data/Example");
}
@Test
public void testAbsoluteBrokenFileLinkIcon() {
Icon programIcon = ProgramContentHandler.PROGRAM_ICON;
MultiIcon multiIcon = new MultiIcon(programIcon);
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
Icon linkIcon = new BrokenLinkIcon(multiIcon);
captureIconAndText(linkIcon, "Example " + RIGHT_ARROW + " /data/Example");
}
@Test
public void testAbsoluteFolderLinkIcon() {
Icon folderIcon = DomainFolder.CLOSED_FOLDER_ICON;
MultiIcon multiIcon = new MultiIcon(folderIcon);
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
captureIconAndText(multiIcon, "Example " + RIGHT_ARROW + " /data/Example");
}
@Test
public void testAbsoluteBrokenFolderLinkIcon() {
Icon folderIcon = DomainFolder.CLOSED_FOLDER_ICON;
MultiIcon multiIcon = new MultiIcon(folderIcon);
multiIcon.addIcon(new TranslateIcon(LinkHandler.LINK_ICON, 0, 1));
Icon linkIcon = new BrokenLinkIcon(multiIcon);
captureIconAndText(linkIcon, "Example " + RIGHT_ARROW + " /data/Example");
}
@Test
public void testProjectDataTable()
throws CancelledException, IOException, InvalidNameException {
@@ -337,8 +372,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
FrontEndPlugin plugin = getPlugin(tool, FrontEndPlugin.class);
JComponent projectDataPanel = (JComponent) getInstanceField("projectDataPanel", plugin);
JTabbedPane tabbedPane =
(JTabbedPane) getInstanceField("projectTab", projectDataPanel);
JTabbedPane tabbedPane = (JTabbedPane) getInstanceField("projectTab", projectDataPanel);
tabbedPane.setSelectedIndex(1);
setToolSize(800, 600);
captureComponent(projectDataPanel);
@@ -407,8 +441,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
TestDummyWizardModel<ProjectWizardData> panelMgr =
new TestDummyWizardModel<ProjectWizardData>(panel, false, true, false,
"Specify Repository Name on Server1", 600, 375,
new ProjectWizardData(), icon);
"Specify Repository Name on Server1", 600, 375, new ProjectWizardData(), icon);
WizardDialog wizard = new WizardDialog(panelMgr, false);
@@ -694,13 +727,12 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
ProjectTestUtils.deleteProject(TEMP_DIR, OTHER_PROJECT);
Project otherProject = ProjectTestUtils.getProject(TEMP_DIR, OTHER_PROJECT);
Language language = getZ80_LANGUAGE();
DomainFile otherFile =
ProjectTestUtils.createProgramFile(otherProject, "Program1", language,
language.getDefaultCompilerSpec(), null);
DomainFile otherFile = ProjectTestUtils.createProgramFile(otherProject, "Program1",
language, language.getDefaultCompilerSpec(), null);
ProjectTestUtils.createProgramFile(otherProject, "Program2", language,
language.getDefaultCompilerSpec(), null);
otherFile.copyToAsLink(projectData.getRootFolder());
otherFile.copyToAsLink(projectData.getRootFolder(), false);
otherProject.close();
@@ -0,0 +1,411 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main.datatree;
import static org.junit.Assert.*;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.tree.TreePath;
import org.junit.*;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingActionIf;
import docking.test.AbstractDockingTest;
import docking.tool.ToolConstants;
import docking.widgets.tree.GTreeNode;
import ghidra.framework.main.FrontEndTool;
import ghidra.framework.main.datatable.ProjectDataContext;
import ghidra.framework.model.*;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.test.*;
import ghidra.util.task.TaskMonitor;
/**
* Tests for opening files.
*/
public class FrontEndPluginOpenProgramActionsTest extends AbstractGhidraHeadedIntegrationTest {
private FrontEndTool frontEndTool;
private TestEnv env;
private DataTree tree;
private DomainFolder rootFolder;
private GTreeNode rootNode;
@Before
public void setUp() throws Exception {
env = new TestEnv();
env.resetDefaultTools();
frontEndTool = env.getFrontEndTool();
env.showFrontEndTool();
setErrorGUIEnabled(false);
tree = findComponent(frontEndTool.getToolFrame(), DataTree.class);
rootFolder = env.getProject().getProjectData().getRootFolder();
Program p = ToyProgramBuilder.buildSimpleProgram("sample", this);
rootFolder.createFile("sample", p, TaskMonitor.DUMMY);
p.release(this);
p = ToyProgramBuilder.buildSimpleProgram("x", this);
rootFolder.createFile("X", p, TaskMonitor.DUMMY);
p.release(this);
rootNode = tree.getViewRoot();
waitForSwing();
tree.expandPath(rootNode.getTreePath());
waitForTree();
}
@After
public void tearDown() throws Exception {
waitForTree();
env.dispose();
}
@Test
public void testOpenActionsEnabled() throws Exception {
setSelectionPath(rootNode.getTreePath());
DockingActionIf openAction = getAction("Open File");
assertTrue(!openAction.isEnabledForContext(getDomainFileActionContext(rootNode)));
ToolChest tc = env.getProject().getLocalToolChest();
ToolTemplate[] configs = tc.getToolTemplates();
for (ToolTemplate config : configs) {
DockingActionIf action = getAction("Open" + config.getName());
assertTrue(!action.isEnabledForContext(getDomainFileActionContext(rootNode)));
assertTrue(!openAction.isEnabledForContext(getDomainFileActionContext(rootNode)));
}
}
@Test
public void testOpenInDefaultTool() throws Exception {
//Open File
GTreeNode npNode = rootNode.getChild("sample");
setSelectionPath(npNode.getTreePath());
waitForTree();
DockingActionIf openAction = getAction("Open File");
performAction(openAction, getFrontEndContext(), true);
verifyToolExistsAndCloseTool();
}
@Test
public void testOpenInDefaultToolMultipleNewTool() throws Exception {
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.NEW_TOOL);
//Open 1st File
DomainFile sampleDf = openInDefaultTool("sample");
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(1, runningTools.length);
assertOpenFiles(runningTools[0], sampleDf);
//Open 2nd File in new tool
DomainFile xDf = openInDefaultTool("X");
// NOTE: runningTools order may vary
runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(2, runningTools.length);
DomainFile[] domainFiles0 = runningTools[0].getDomainFiles();
assertEquals(1, domainFiles0.length);
DomainFile[] domainFiles1 = runningTools[1].getDomainFiles();
assertEquals(1, domainFiles1.length);
if (sampleDf.equals(domainFiles0[0])) {
assertEquals(xDf, domainFiles1[0]);
}
else if (sampleDf.equals(domainFiles1[0])) {
assertEquals(xDf, domainFiles0[0]);
}
else {
fail("Unexpected open domain files");
}
exitTools(runningTools);
}
@Test
public void testOpenInDefaultToolMultipleReuseTool() throws Exception {
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.REUSE_TOOL);
//Open 1st File
DomainFile sampleDf = openInDefaultTool("sample");
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(1, runningTools.length);
assertOpenFiles(runningTools[0], sampleDf);
//Open 2nd File in same tool
DomainFile xDf = openInDefaultTool("X");
runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(1, runningTools.length);
assertOpenFiles(runningTools[0], sampleDf, xDf);
exitTools(runningTools);
}
@Test
public void testOpenMultipleNewTool() throws Exception {
Program p = ToyProgramBuilder.buildSimpleProgram("y", this);
rootFolder.createFile("Y", p, TaskMonitor.DUMMY);
p.release(this);
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.NEW_TOOL);
//Open 1st File
DomainFile sampleDf = openInDefaultTool("sample");
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(1, runningTools.length);
assertOpenFiles(runningTools[0], sampleDf);
String toolName = runningTools[0].getName();
//Open two additional files in new tool
openInTool(toolName, "X", "Y");
// NOTE: runningTools order may vary
runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(2, runningTools.length);
DomainFile[] domainFiles0 = runningTools[0].getDomainFiles();
DomainFile[] domainFiles1 = runningTools[1].getDomainFiles();
assertEquals(3, domainFiles0.length + domainFiles1.length);
exitTools(runningTools);
}
@Test
public void testOpenMultipleReuseTool() throws Exception {
Program p = ToyProgramBuilder.buildSimpleProgram("y", this);
rootFolder.createFile("Y", p, TaskMonitor.DUMMY);
p.release(this);
ToolOptions options = frontEndTool.getOptions(ToolConstants.TOOL_OPTIONS);
options.setEnum(FrontEndTool.DEFAULT_TOOL_LAUNCH_MODE, DefaultLaunchMode.REUSE_TOOL);
//Open 1st File
DomainFile sampleDf = openInDefaultTool("sample");
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(1, runningTools.length);
assertOpenFiles(runningTools[0], sampleDf);
String toolName = runningTools[0].getName();
//Open two additional files in same tool
openInTool(toolName, "X", "Y");
runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(1, runningTools.length);
DomainFile[] domainFiles0 = runningTools[0].getDomainFiles();
assertEquals(3, domainFiles0.length);
exitTools(runningTools);
}
@Test
public void testOpenWith() throws Exception {
GTreeNode npNode = rootNode.getChild("sample");
setSelectionPath(npNode.getTreePath());
waitForTree();
ToolChest tc = env.getProject().getLocalToolChest();
ToolTemplate[] configs = tc.getToolTemplates();
DockingActionIf action = getAction("Open" + configs[0].getName());
performAction(action, getFrontEndContext(), true);
verifyToolExistsAndCloseTool();
}
@Test
public void testOpenWithDoubleClick() throws Exception {
// make sure that the Code Browser tool is the default
ToolChest tc = env.getProject().getLocalToolChest();
ToolTemplate[] configs = tc.getToolTemplates();
ToolTemplate codeBrowserConfig = null;
for (ToolTemplate config : configs) {
if ("CodeBrowser".equals(config.getName())) {
codeBrowserConfig = config;
}
}
if (codeBrowserConfig == null) {
Assert.fail("Unable to find the Code Browser config file.");
}
// double click on the program node
GTreeNode npNode = rootNode.getChild("sample");
JTree jTree = (JTree) invokeInstanceMethod("getJTree", tree);
Rectangle rect = jTree.getPathBounds(npNode.getTreePath());
setSelectionPath(npNode.getTreePath());
waitForTree();
clickMouse(jTree, MouseEvent.BUTTON1, rect.x, rect.y, 2, 0);
// make sure that the tool is loaded and processes all of the tasks it launches
Window window = waitForToolLaunch();
// DEBUG:
if (window == null) {
// see if any tools have been launched
PluginTool[] runningTools = frontEndTool.getToolServices().getRunningTools();
for (PluginTool tool : runningTools) {
System.err.println("\t\"" + tool.getName() + "\"");
JFrame toolFrame = tool.getToolFrame();
System.err.println("\t\twith window: " + toolFrame.getTitle());
}
System.err.println("Open Windows: ");
System.err.println(getOpenWindowsAsString());
}
assertNotNull(window);
waitForBusyTool(env.getProject().getToolManager().getRunningTools()[0]);
waitForTasks();
verifyToolExistsAndCloseTool();
}
private Window waitForToolLaunch() {
waitForSwing();
long start = System.currentTimeMillis();
int tryCount = 0;
Window window = null;
while (window == null && tryCount < 5) {
++tryCount;
window = waitForValueWithoutFailing(() -> {
return getWindowByTitleContaining(null,
"CodeBrowser: " + PROJECT_NAME + ":/sample");
});
}
long total = System.currentTimeMillis() - start;
assertNotNull("Timed-out waiting for tool - " + total + " ms", window);
return window;
}
//==================================================================================================
// Private Methods
//==================================================================================================
private ActionContext getFrontEndContext() {
ComponentProvider provider = env.getFrontEndProvider();
return runSwing(() -> provider.getActionContext(null));
}
private DomainFile openInDefaultTool(String fileName) throws Exception {
GTreeNode node = rootNode.getChild(fileName);
assertTrue("Expected domain file node", node instanceof DomainFileNode);
DomainFileNode fileNode = (DomainFileNode) node;
DomainFile domainFile = fileNode.getDomainFile();
setSelectionPath(fileNode.getTreePath());
waitForTree();
DockingActionIf openAction = getAction("Open File");
performAction(openAction, getFrontEndContext(), true);
return domainFile;
}
private List<DomainFile> openInTool(String toolName, String... fileNames) throws Exception {
ToolServices toolServices = env.getProject().getToolServices();
ArrayList<DomainFile> domainFiles = new ArrayList<>();
for (String fileName : fileNames) {
DomainFile df = rootFolder.getFile(fileName);
assertNotNull(df);
domainFiles.add(df);
}
runSwing(() -> toolServices.launchTool(toolName, domainFiles));
waitForSwing();
return domainFiles;
}
private void assertOpenFiles(PluginTool tool, DomainFile... expectedDomainFiles) {
DomainFile[] domainFiles = tool.getDomainFiles();
assertArrayEquals(expectedDomainFiles, domainFiles);
}
private void exitTools(PluginTool... tools) {
runSwing(() -> {
for (PluginTool t : tools) {
t.close();
}
});
}
private void verifyToolExistsAndCloseTool() {
PluginTool[] runningTools = env.getProject().getToolManager().getRunningTools();
assertEquals(1, runningTools.length);
exitTools(runningTools[0]);
}
private ActionContext getDomainFileActionContext(GTreeNode... nodes) {
List<DomainFile> fileList = new ArrayList<>();
List<DomainFolder> folderList = new ArrayList<>();
for (GTreeNode node : nodes) {
if (node instanceof DomainFileNode fileNode) {
fileList.add(fileNode.getDomainFile());
}
else if (node instanceof DomainFolderNode folderNode) {
folderList.add(folderNode.getDomainFolder());
}
}
return new ProjectDataContext(null, null, nodes[0], folderList, fileList, tree, true);
}
private DockingActionIf getAction(String actionName) {
DockingActionIf action =
AbstractDockingTest.getAction(frontEndTool, "FrontEndPlugin", actionName);
return action;
}
private void setSelectionPath(final TreePath path) throws Exception {
SwingUtilities.invokeAndWait(() -> tree.setSelectionPath(path));
}
private void waitForTree() {
waitForSwing();
while (tree.isBusy()) {
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
// don't care
}
}
waitForSwing();
}
}
@@ -0,0 +1,400 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main.datatree;
import static org.junit.Assert.*;
import org.junit.*;
import docking.ActionContext;
import docking.action.DockingActionIf;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.data.LinkHandler;
import ghidra.framework.data.LinkHandler.LinkStatus;
import ghidra.framework.model.*;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.listing.Program;
import ghidra.server.remote.ServerTestUtil;
import ghidra.test.*;
import ghidra.util.task.TaskMonitor;
public class ProjectCopyPasteTest extends AbstractGhidraHeadedIntegrationTest {
private FrontEndTestEnv env;
private DomainFolder abcFolder;
private DomainFile programFile;
@Before
public void setUp() throws Exception {
env = new FrontEndTestEnv();
/**
/abc (folder)
foo (program file)
/xyz (empty folder)
**/
DomainFolder rootFolder = env.getRootFolder();
abcFolder = rootFolder.createFolder("abc");
rootFolder.createFolder("xyz");
Program p = ToyProgramBuilder.buildSimpleProgram("foo", this);
programFile = abcFolder.createFile("foo", p, TaskMonitor.DUMMY);
p.release(this);
env.waitForTree();
}
@After
public void tearDown() throws Exception {
env.dispose();
ClientUtil.clearRepositoryAdapter("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
}
@Test
public void testCopyPasteFile() throws Exception {
// Select /abc/foo file and Copy
DomainFileNode fooFile = env.waitForFileNode("/abc/foo");
final ActionContext copyActionContext = env.getDomainFileActionContext(fooFile);
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
assertNotNull("Copy action not found", copyAction);
assertTrue(copyAction.isAddToPopup(copyActionContext));
assertTrue(copyAction.isEnabledForContext(copyActionContext));
runSwing(() -> copyAction.actionPerformed(copyActionContext));
// Select /xyz folder and perform Paste
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
final ActionContext pasteActionContext = env.getDomainFileActionContext(xyzNode);
DockingActionIf pasteAction = getAction(env.getFrontEndTool(), "Paste");
assertNotNull("Paste action not found", pasteAction);
assertTrue(pasteAction.isAddToPopup(pasteActionContext));
assertTrue(pasteAction.isEnabledForContext(pasteActionContext));
runSwing(() -> pasteAction.actionPerformed(pasteActionContext));
DomainFileNode fooCopyNode = env.waitForFileNode("/xyz/foo");
DomainFile file = fooCopyNode.getDomainFile();
assertTrue(file.exists());
assertFalse(file.isLink());
assertEquals(LinkStatus.NON_LINK, LinkHandler.getLinkFileStatus(file, null));
}
@Test
public void testCopyPastInternalAbsoluteFileLink() throws Exception {
testCopyPastInternalFileLink("Paste Link");
}
@Test
public void testCopyPastInternalRelativeFileLink() throws Exception {
testCopyPastInternalFileLink("Paste Relative-Link");
}
private void testCopyPastInternalFileLink(String pastActionName) throws Exception {
/**
/abc
foo (copied)
/xyz (pasted into)
foo -> (direct link)
foo.1 -> (link to direct link)
**/
boolean isRelative = pastActionName.contains("Relative");
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
assertNotNull("Copy action not found", copyAction);
// Select /abc/foo file and perform Copy
DomainFileNode fooNode = env.waitForFileNode("/abc/foo");
final ActionContext copyActionContext = env.getDomainFileActionContext(fooNode);
assertTrue(copyAction.isAddToPopup(copyActionContext));
assertTrue(copyAction.isEnabledForContext(copyActionContext));
runSwing(() -> copyAction.actionPerformed(copyActionContext));
DockingActionIf pasteLinkAction = getAction(env.getFrontEndTool(), pastActionName);
assertNotNull(pastActionName + " action not found", pasteLinkAction);
// Select /xyz folder and perform Paste as Link
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
final ActionContext pasteLinkActionContext = env.getDomainFileActionContext(xyzNode);
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
DomainFileNode fooLinkNode = env.waitForFileNode("/xyz/foo");
DomainFile file = fooLinkNode.getDomainFile();
assertTrue(file.exists());
assertTrue(file.isLink());
LinkFileInfo linkInfo = file.getLinkInfo();
assertFalse(linkInfo.isFolderLink());
assertFalse(linkInfo.isExternalLink());
assertEquals(isRelative ? "../abc/foo" : "/abc/foo", linkInfo.getLinkPath());
assertNull(linkInfo.getLinkedFolder());
ProjectData projectData = env.getFrontEndTool().getProject().getProjectData();
DomainFile fooLinkFile = projectData.getFile("/xyz/foo");
assertNotNull(fooLinkFile);
assertTrue(fooLinkFile.exists());
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(fooLinkFile, null));
DomainObject dobj = fooLinkFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
try {
assertTrue(dobj instanceof ProgramDB);
assertTrue(dobj.canSave());
assertTrue(dobj.isChangeable());
assertEquals(programFile, dobj.getDomainFile());
}
finally {
if (dobj != null) {
dobj.release(this);
}
}
// Select /xyz/foo file and perform Copy
final ActionContext copy2ActionContext = env.getDomainFileActionContext(fooLinkNode);
assertTrue(copyAction.isAddToPopup(copy2ActionContext));
assertTrue(copyAction.isEnabledForContext(copy2ActionContext));
runSwing(() -> copyAction.actionPerformed(copy2ActionContext));
// Select /xyz folder and perform Paste as Link
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
fooLinkNode = env.waitForFileNode("/xyz/foo.1");
file = fooLinkNode.getDomainFile();
assertTrue(file.exists());
assertTrue(file.isLink());
linkInfo = file.getLinkInfo();
assertFalse(linkInfo.isFolderLink());
assertFalse(linkInfo.isExternalLink());
assertEquals(isRelative ? "foo" : "/xyz/foo", linkInfo.getLinkPath());
assertNull(linkInfo.getLinkedFolder());
fooLinkFile = projectData.getFile("/xyz/foo.1");
assertNotNull(fooLinkFile);
assertTrue(fooLinkFile.exists());
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(fooLinkFile, null));
dobj = fooLinkFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
try {
assertTrue(dobj instanceof ProgramDB);
assertTrue(dobj.canSave());
assertTrue(dobj.isChangeable());
assertEquals(programFile, dobj.getDomainFile());
}
finally {
if (dobj != null) {
dobj.release(this);
}
}
}
@Test
public void testCopyPastFolder() throws Exception {
// Select /abc file from viewed project and Copy
DomainFolderNode abcFolderNode = env.waitForFolderNode("/abc");
final ActionContext copyActionContext = env.getDomainFileActionContext(abcFolderNode);
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
assertNotNull("Copy action not found", copyAction);
assertTrue(copyAction.isAddToPopup(copyActionContext));
assertTrue(copyAction.isEnabledForContext(copyActionContext));
runSwing(() -> copyAction.actionPerformed(copyActionContext));
// Select /xyz folder and perform Paste
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
final ActionContext pasteActionContext = env.getDomainFileActionContext(xyzNode);
DockingActionIf pasteAction = getAction(env.getFrontEndTool(), "Paste");
assertNotNull("Paste action not found", pasteAction);
assertTrue(pasteAction.isAddToPopup(pasteActionContext));
assertTrue(pasteAction.isEnabledForContext(pasteActionContext));
runSwing(() -> pasteAction.actionPerformed(pasteActionContext));
DomainFolderNode abcCopyNode = env.waitForFolderNode("/xyz/abc");
DomainFolder folder = abcCopyNode.getDomainFolder();
assertTrue(!folder.isEmpty());
DomainFile file = folder.getFile("foo");
assertNotNull(file);
assertTrue(file.exists());
}
@Test
public void testCopyPastInternalAbsoluteFolderLink() throws Exception {
testCopyPastInternalFolderLink("Paste Link");
}
@Test
public void testCopyPastInternalRelativeFolderLink() throws Exception {
testCopyPastInternalFolderLink("Paste Relative-Link");
}
private void testCopyPastInternalFolderLink(String pastActionName) throws Exception {
/**
/abc (copied)
foo
/xyz (pasted into)
abc -> (direct link)
abc.1 -> (link to direct link)
**/
boolean isRelative = pastActionName.contains("Relative");
DockingActionIf copyAction = getAction(env.getFrontEndTool(), "Copy");
assertNotNull("Copy action not found", copyAction);
// Select /abc folder and perform Copy
DomainFolderNode abcNode = env.waitForFolderNode("/abc");
final ActionContext copyActionContext = env.getDomainFileActionContext(abcNode);
assertTrue(copyAction.isAddToPopup(copyActionContext));
assertTrue(copyAction.isEnabledForContext(copyActionContext));
runSwing(() -> copyAction.actionPerformed(copyActionContext));
// Select /xyz folder and perform Paste as Link /xyz/abc
DockingActionIf pasteLinkAction = getAction(env.getFrontEndTool(), pastActionName);
assertNotNull(pastActionName + " action not found", pasteLinkAction);
DomainFolderNode xyzNode = env.waitForFolderNode("/xyz");
final ActionContext pasteLinkActionContext = env.getDomainFileActionContext(xyzNode);
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
final DomainFileNode xyzAbcLinkNode = env.waitForFileNode("/xyz/abc");
final DomainFile xyzAbcLinkFile = xyzAbcLinkNode.getDomainFile();
assertTrue(xyzAbcLinkFile.exists());
assertTrue(xyzAbcLinkFile.isLink());
LinkFileInfo xyzAbcLinkInfo = xyzAbcLinkFile.getLinkInfo();
assertTrue(xyzAbcLinkInfo.isFolderLink());
assertFalse(xyzAbcLinkInfo.isExternalLink());
assertEquals(isRelative ? "../abc" : "/abc", xyzAbcLinkInfo.getLinkPath());
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(xyzAbcLinkFile, null));
final LinkedDomainFolder xyzAbcLinkedFolder = xyzAbcLinkInfo.getLinkedFolder();
assertNotNull(xyzAbcLinkedFolder);
assertTrue(xyzAbcLinkedFolder.isLinked());
assertEquals(abcFolder, xyzAbcLinkedFolder.getRealFolder());
ProjectData projectData = env.getFrontEndTool().getProject().getProjectData();
DomainFile fooFile = projectData.getFile("/xyz/abc/foo");
assertNotNull(fooFile);
assertTrue(fooFile.exists());
DomainObject dobj = fooFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
try {
assertTrue(dobj instanceof ProgramDB);
assertTrue(dobj.canSave());
assertTrue(dobj.isChangeable());
assertEquals(programFile, dobj.getDomainFile());
}
finally {
if (dobj != null) {
dobj.release(this);
}
}
// Select /xyz/abc linked-folder and perform Copy
final ActionContext copy2ActionContext = env.getDomainFileActionContext(xyzAbcLinkNode);
assertTrue(copyAction.isAddToPopup(copy2ActionContext));
assertTrue(copyAction.isEnabledForContext(copy2ActionContext));
runSwing(() -> copyAction.actionPerformed(copy2ActionContext));
// Select /xyz and perform Paste as Link /xyz/abc.1
assertTrue(pasteLinkAction.isAddToPopup(pasteLinkActionContext));
assertTrue(pasteLinkAction.isEnabledForContext(pasteLinkActionContext));
runSwing(() -> pasteLinkAction.actionPerformed(pasteLinkActionContext));
final DomainFileNode xyzAbc1CopyNode = env.waitForFileNode("/xyz/abc.1");
DomainFile xyzAbc1LinkFile = xyzAbc1CopyNode.getDomainFile();
assertTrue(xyzAbc1LinkFile.exists());
assertTrue(xyzAbc1LinkFile.isLink());
LinkFileInfo xyzAbc1LinkInfo = xyzAbc1LinkFile.getLinkInfo();
assertTrue(xyzAbc1LinkInfo.isFolderLink());
assertFalse(xyzAbc1LinkInfo.isExternalLink());
assertEquals(isRelative ? "abc" : "/xyz/abc", xyzAbc1LinkInfo.getLinkPath());
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(xyzAbc1LinkFile, null));
final LinkedDomainFolder xyzAbc1LinkedFolder = xyzAbc1LinkInfo.getLinkedFolder();
assertNotNull(xyzAbc1LinkedFolder);
assertTrue(xyzAbc1LinkedFolder.isLinked());
assertEquals(xyzAbcLinkedFolder.getRealFolder(), xyzAbc1LinkedFolder.getRealFolder());
fooFile = projectData.getFile("/xyz/abc.1/foo");
assertNotNull(fooFile);
assertTrue(fooFile.exists());
dobj = fooFile.getDomainObject(this, false, false, TaskMonitor.DUMMY);
try {
assertTrue(dobj instanceof ProgramDB);
assertTrue(dobj.canSave());
assertTrue(dobj.isChangeable());
assertEquals(programFile, dobj.getDomainFile());
}
finally {
if (dobj != null) {
dobj.release(this);
}
}
}
}
@@ -0,0 +1,439 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main.datatree;
import static org.junit.Assert.*;
import java.util.function.BooleanSupplier;
import org.junit.*;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.data.FolderLinkContentHandler;
import ghidra.framework.data.LinkHandler;
import ghidra.framework.data.LinkHandler.LinkStatus;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.database.DataTypeArchiveDB;
import ghidra.program.database.ProgramLinkContentHandler;
import ghidra.program.model.listing.Program;
import ghidra.server.remote.ServerTestUtil;
import ghidra.test.*;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.TaskMonitor;
public class ProjectLinkFileStatusTest extends AbstractGhidraHeadedIntegrationTest {
private FrontEndTestEnv env;
private DomainFolder abcFolder;
private DomainFolder xyzFolder;
private DomainFile programFile;
@Before
public void setUp() throws Exception {
env = new FrontEndTestEnv();
/**
/abc/ (folder)
abc -> /xyz/abc (circular)
foo (program file)
/xyz/
abc -> /abc (folder link)
abc -> (circular)
foo
foo -> /abc/foo (program link)
**/
DomainFolder rootFolder = env.getRootFolder();
abcFolder = rootFolder.createFolder("abc");
xyzFolder = rootFolder.createFolder("xyz");
DomainFile abcLinkFile = abcFolder.copyToAsLink(xyzFolder, false);
abcLinkFile.copyToAsLink(abcFolder, false);
Program p = ToyProgramBuilder.buildSimpleProgram("foo", this);
programFile = abcFolder.createFile("foo", p, TaskMonitor.DUMMY);
p.release(this);
programFile.copyToAsLink(xyzFolder, false);
env.waitForTree();
}
@After
public void tearDown() throws Exception {
if (env != null) {
env.dispose();
}
ClientUtil.clearRepositoryAdapter("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
}
@Test
public void testNonFileLink() throws Exception {
DomainFileNode fileNode = env.waitForFileNode("/abc/foo");
assertEquals(LinkStatus.NON_LINK,
LinkHandler.getLinkFileStatus(fileNode.getDomainFile(), null));
}
@Test
public void testExternalFileLink() throws Exception {
//
// Create external program file-link /abc/A to remote repository
//
DomainFile linkFile = abcFolder.createLinkFile("ghidra://localhost/Test/A", "A",
ProgramLinkContentHandler.INSTANCE);
env.waitForTree(); // give time for ChangeManager to update
//
// Verify /abc/A external program file-link exists with correct status and display name
//
DomainFileNode nodeA = waitForFileNode("/abc/A");
assertFalse(nodeA.isFolderLink());
assertEquals(linkFile, nodeA.getDomainFile());
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
String displayName = runSwing(() -> nodeA.getDisplayText());
assertTrue("Unexpected node display name: " + displayName,
displayName.contains("localhost[Test]:/A"));
//
// Create external program file-link /abc/B to local project
//
linkFile = abcFolder.createLinkFile("ghidra:/x/y/Test?/B", "B",
ProgramLinkContentHandler.INSTANCE);
env.waitForTree(); // give time for ChangeManager to update
//
// Verify /abc/B external program file-link exists with correct status and display name
//
DomainFileNode nodeB = waitForFileNode("/abc/B");
assertFalse(nodeB.isFolderLink());
assertEquals(linkFile, nodeB.getDomainFile());
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
displayName = runSwing(() -> nodeB.getDisplayText());
assertTrue("Unexpected node display name: " + displayName, displayName.contains("Test:/B"));
//
// Remove /abc/foo file
//
DomainFile fooFile = abcFolder.getFile("foo");
assertNotNull(fooFile);
fooFile.delete();
//
// Replace deleted file with external program file-link to local project
// which sets-up indirect link path from /xyz/foo -> /abc/foo -> local project file
//
linkFile = abcFolder.createLinkFile("ghidra:/x/y/Test?/foo", "foo",
ProgramLinkContentHandler.INSTANCE);
waitForSwing(); // give a chance for ChangeManager to be notified
env.waitForTree(); // give time for ChangeManager to update
//
// Verify /abc/foo external program file-link exists with correct status and display name
//
DomainFileNode fooNode = waitForFileNode("/abc/foo");
assertFalse(fooNode.isFolderLink());
assertEquals(linkFile, fooNode.getDomainFile());
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
displayName = runSwing(() -> fooNode.getDisplayText());
if (!displayName.contains("Test:/foo")) {
int junk = 0;
}
assertTrue("Unexpected node display name: " + displayName,
displayName.contains("Test:/foo"));
//
// Check pre-existing file-link /xyz/foo reflects external status
//
DomainFileNode fooLinkNode = waitForFileNode("/xyz/foo");
assertEquals(LinkStatus.EXTERNAL,
LinkHandler.getLinkFileStatus(fooLinkNode.getDomainFile(), null));
}
@Test
public void testExternalFolderLink() throws Exception {
// NOTE: Only refer to root repo folder with remote URL to avoid unwanted connection attempt
//
// Create external folder-link /abc/A to remote repository
//
DomainFile linkFile = abcFolder.createLinkFile("ghidra://localhost/Test/", "A",
FolderLinkContentHandler.INSTANCE);
env.waitForTree(); // give time for ChangeManager to update
//
// Verify /abc/A external folder-link exists with correct status and display name
//
DomainFileNode nodeA = waitForFileNode("/abc/A");
assertTrue(nodeA.isFolderLink());
assertEquals(linkFile, nodeA.getDomainFile());
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
String displayName = runSwing(() -> nodeA.getDisplayText());
assertTrue("Unexpected node display name: " + displayName,
displayName.contains("localhost[Test]:/"));
//
// Create external folder-link /abc/B to local project
//
linkFile =
abcFolder.createLinkFile("ghidra:/x/y/Test?/B", "B", FolderLinkContentHandler.INSTANCE);
env.waitForTree(); // give time for ChangeManager to update
//
// Verify /abc/B external folder-link exists with correct status and display name
//
DomainFileNode nodeB = waitForFileNode("/abc/B");
assertTrue(nodeB.isFolderLink());
assertEquals(linkFile, nodeB.getDomainFile());
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
displayName = runSwing(() -> nodeB.getDisplayText());
assertTrue("Unexpected node display name: " + displayName, displayName.contains("Test:/B"));
//
// Remove /abc folder and its children
//
DomainFolder rootFolder = abcFolder.getParent();
abcFolder.getFile("abc").delete();
abcFolder.getFile("foo").delete();
abcFolder.getFile("A").delete();
abcFolder.getFile("B").delete();
abcFolder.delete();
//
// Remove /xyz/foo file to avoid remote access attempt to ghidra://localhost/Test/foo
// after /abc is replaced in the next step
//
DomainFile fooFile = xyzFolder.getFile("foo");
assertNotNull(fooFile);
fooFile.delete();
//
// Replace deleted folder with external folder-link to local project
// which sets-up indirect link path from /xyz/abc -> /abc -> local project root folder
//
linkFile = rootFolder.createLinkFile("ghidra://localhost/Test/", "abc",
FolderLinkContentHandler.INSTANCE);
env.waitForTree(); // give time for ChangeManager to update
//
// Verify /abc external folder-link exists with correct status and display name
//
DomainFileNode abcLinkNode = waitForFileNode("/abc");
assertTrue(abcLinkNode.isFolderLink());
assertEquals(linkFile, abcLinkNode.getDomainFile());
assertEquals(LinkStatus.EXTERNAL, LinkHandler.getLinkFileStatus(linkFile, null));
displayName = runSwing(() -> abcLinkNode.getDisplayText());
assertTrue("Unexpected node display name: " + displayName,
displayName.contains("localhost[Test]:/"));
//
// Check pre-existing folder-link /xyz/abc reflects external status
//
DomainFileNode abcLinkNode2 = waitForFileNode("/xyz/abc");
assertEquals(LinkStatus.EXTERNAL,
LinkHandler.getLinkFileStatus(abcLinkNode2.getDomainFile(), null));
}
@Test
public void testBrokenFolderLink() throws Exception {
//
// Verify broken folder-link status for /abc/abc which has circular reference
//
DomainFileNode abcAbcLinkNode = waitForFileNode("/abc/abc");
assertTrue(abcAbcLinkNode.isFolderLink());
String displayName = runSwing(() -> abcAbcLinkNode.getDisplayText());
assertTrue("Unexpected node display name: " + displayName,
displayName.endsWith(" /xyz/abc"));
assertEquals(LinkStatus.BROKEN,
LinkHandler.getLinkFileStatus(abcAbcLinkNode.getDomainFile(), null));
String tooltip = abcAbcLinkNode.getToolTip().replace("&nbsp;", " ");
assertTrue(tooltip.contains("circular"));
//
// Verify good folder-link internal status for /xyz/abc which has circular reference
//
DomainFileNode xyzAbcLinkNode = waitForFileNode("/xyz/abc");
assertTrue(xyzAbcLinkNode.isFolderLink());
displayName = runSwing(() -> xyzAbcLinkNode.getDisplayText());
assertTrue("Unexpected node display name: " + displayName, displayName.endsWith(" /abc"));
assertEquals(LinkStatus.INTERNAL,
LinkHandler.getLinkFileStatus(xyzAbcLinkNode.getDomainFile(), null));
//
// Verify broken folder-link status for /xyz/abc/abc which has circular reference
//
DomainFileNode abcLinkedNode = waitForFileNode("/xyz/abc/abc");
assertTrue(abcLinkedNode.isFolderLink());
displayName = runSwing(() -> abcLinkedNode.getDisplayText());
assertTrue("Unexpected node display name: " + displayName,
displayName.endsWith(" /xyz/abc"));
assertEquals(LinkStatus.BROKEN,
LinkHandler.getLinkFileStatus(abcLinkedNode.getDomainFile(), null));
tooltip = abcLinkedNode.getToolTip().replace("&nbsp;", " ");
assertTrue(tooltip.contains("circular"));
//
// Rename folder /abc to /ABC causing folder-link /xyz/abc to become broken
//
abcFolder = abcFolder.setName("ABC");
env.waitForTree(); // give time for ChangeManager to update
// Verify /abc node not found
assertNull(env.getRootNode().getChild("abc"));
//
// Verify broken folder-link status for /ABC/abc which has circular reference
//
DomainFileNode ABCAbcLinkNode = waitForFileNode("/ABC/abc");
assertTrue(ABCAbcLinkNode.isFolderLink());
displayName = runSwing(() -> ABCAbcLinkNode.getDisplayText());
assertTrue("Unexpected node display name: " + displayName,
displayName.endsWith(" /xyz/abc"));
assertEquals(LinkStatus.BROKEN,
LinkHandler.getLinkFileStatus(ABCAbcLinkNode.getDomainFile(), null));
tooltip = ABCAbcLinkNode.getToolTip().replace("&nbsp;", " ");
assertTrue(tooltip.contains("folder not found: /abc"));
env.waitForTree(); // give time for ChangeManager to update
//
// Verify that folder-link /xyz/abc is broken due to missing /abc
//
DomainFileNode n = waitForFileNode("/xyz/abc"); // wait for refresh
assertTrue(n == xyzAbcLinkNode);
assertTrue(xyzAbcLinkNode.isFolderLink());
assertEquals(LinkStatus.BROKEN,
LinkHandler.getLinkFileStatus(xyzAbcLinkNode.getDomainFile(), null));
tooltip = xyzAbcLinkNode.getToolTip().replace("&nbsp;", " ");
assertTrue("Unexpected tooltip: " + tooltip, tooltip.contains("folder not found: /abc"));
//
// Attempt conflicting folder-link creation
//
DomainFile linkFile = abcFolder.getParent()
.createLinkFile("ghidra://localhost/Test/ABC", "ABC",
FolderLinkContentHandler.INSTANCE);
assertEquals("ABC.1", linkFile.getName()); // link forced to have unqiue name
//
// Try to force folder vs folder-link name conflict
// While it won't be allowed it could occur in-the-wild due to shared project content
// (case not tested here)
//
try {
linkFile.setName("ABC"); // trigger folder name conflict for folder-link
fail("Expected DuplicateFileException");
}
catch (DuplicateFileException e) {
// expected for link file
}
try {
abcFolder.setName("ABC.1");
fail("Expected DuplicateFileException");
}
catch (DuplicateFileException e) {
// expected for link file
}
}
@Test
public void testBrokenFileLink() throws Exception {
//
// Verify good internal file-link status for /xyz/foo -> /abc/foo
//
DomainFileNode linkNode = waitForFileNode("/xyz/foo");
assertEquals(LinkStatus.INTERNAL,
LinkHandler.getLinkFileStatus(linkNode.getDomainFile(), null));
//
// Copy program file /abc/foo as relative file-link /abc/foo.1 and verify good internal file-link status
//
DomainFile relativeProgramLink = programFile.copyToAsLink(abcFolder, true);
assertEquals("/abc/foo.1", relativeProgramLink.getPathname());
assertEquals(LinkStatus.INTERNAL, LinkHandler.getLinkFileStatus(relativeProgramLink, null));
//
// Delete program file /abc/foo and verify that file-link /abc/foo.1 becomes broken
//
programFile.delete();
assertEquals(LinkStatus.BROKEN, LinkHandler.getLinkFileStatus(relativeProgramLink, null));
env.waitForTree(); // give time for ChangeManager to update
//
// Verify broken /xyz/foo file link status due to deleted file /abc/foo
//
linkNode = waitForFileNode("/xyz/foo");
assertEquals(LinkStatus.BROKEN,
LinkHandler.getLinkFileStatus(linkNode.getDomainFile(), null));
String tooltip = linkNode.getToolTip().replace("&nbsp;", " ");
assertTrue(tooltip.contains("file not found: /abc/foo"));
//
// Create DataTypeArchive project file /abc/foo
//
DataTypeArchiveDB dtm = new DataTypeArchiveDB(abcFolder, "foo", this);
dtm.save(null, TaskMonitor.DUMMY);
dtm.release(this);
env.waitForTree(); // give time for ChangeManager to update
//
// Verify that Program file-link is now broken due to incompatible content for /abc/foo
//
linkNode = waitForFileNode("/xyz/foo");
assertEquals(LinkStatus.BROKEN,
LinkHandler.getLinkFileStatus(linkNode.getDomainFile(), null));
env.waitForSwing();
tooltip = linkNode.getToolTip().replace("&nbsp;", " ");
assertTrue("Unexpected tooltip: " + tooltip,
tooltip.contains("incompatible content-type: /abc/foo"));
}
private DomainFileNode waitForFileNode(String path) {
DomainFileNode fileNode = env.waitForFileNode(path);
waitForRefresh(fileNode);
return fileNode;
}
private void waitForRefresh(DomainFileNode fileNode) {
waitFor(new BooleanSupplier() {
@Override
public boolean getAsBoolean() {
return !fileNode.hasPendingRefresh();
}
});
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -130,11 +130,11 @@ public class DataTreeHelper {
for (int i = 0; i < nodes.length; i++) {
GTreeNode node = nodes[i];
treePaths[i] = node.getTreePath();
if (node instanceof DomainFileNode) {
fileList.add(((DomainFileNode) node).getDomainFile());
if (node instanceof DomainFileNode fileNode) {
fileList.add(fileNode.getDomainFile());
}
else if (node instanceof DomainFolderNode) {
folderList.add(((DomainFolderNode) node).getDomainFolder());
else if (node instanceof DomainFolderNode folderNode) {
folderList.add(folderNode.getDomainFolder());
}
}