Merge remote-tracking branch 'origin/GP-2903_ghidra1_ImmutableDomainObjects--SQUASHED'

This commit is contained in:
Ryan Kurtz 2022-12-15 06:01:56 -05:00
commit b707c2ea6b
11 changed files with 226 additions and 256 deletions

View file

@ -169,18 +169,7 @@ public class OpenProgramTask extends Task {
String path = url != null ? url.toString() : domainFile.getPathname();
Object obj = null;
try {
obj = domainFile.getReadOnlyDomainObject(consumer, version, taskMonitor);
if (obj == null) {
String errorMessage = "Can't open " + contentType + " - \"" + path + "\"";
if (version != DomainFile.DEFAULT_VERSION) {
errorMessage += " version " + version;
}
Msg.showError(this, null, "File Not Found", errorMessage);
}
}
catch (CancelledException e) {
// we don't care, the task has been cancelled

View file

@ -100,13 +100,17 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends DataTreeDia
/**
* Get the selected domain object for read-only or immutable use.
* If an existing open object is selected its original mode applies but consumer will
* be added.
* @param consumer consumer
* @param readOnly true if the domain object should be opened read only, versus immutable
* @return null if a file was not selected
* be added. The caller/consumer is responsible for releasing the returned domain object
* when done using it (see {@link DomainObject#release(Object)}).
* @param consumer domain object consumer
* @param immutable true if the domain object should be opened immutable, else false for
* read-only. Immutable mode should not be used for content that will be modified. If
* read-only indicated an upgrade will always be performed if required.
* @return opened domain object or null if a file was not selected or if open failed to
* complete.
*/
@SuppressWarnings("unchecked") // relies on content class filter
public T getDomainObject(Object consumer, boolean readOnly) {
public T getDomainObject(Object consumer, boolean immutable) {
T dobj = null;
if (usingOpenProgramList()) {
dobj = getSelectedOpenDomainObject();
@ -115,20 +119,19 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends DataTreeDia
}
return dobj;
}
int version = DomainFile.DEFAULT_VERSION;
if (historyPanel != null) {
dobj = (T) historyPanel.getSelectedVersion(consumer, readOnly);
version = historyPanel.getSelectedVersionNumber();
}
if (dobj == null) {
DomainFile domainFile = getDomainFile();
if (domainFile != null) {
GetVersionedObjectTask task =
new GetVersionedObjectTask(consumer, domainFile, DomainFile.DEFAULT_VERSION,
readOnly);
GetDomainObjectTask task =
new GetDomainObjectTask(consumer, domainFile, version, immutable);
tool.execute(task, 1000);
return (T) task.getVersionedObject();
return (T) task.getDomainObject();
}
}
return dobj;
return null;
}
/**

View file

@ -17,7 +17,6 @@ package ghidra.app.plugin.core.diff;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
@ -51,6 +50,7 @@ import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.*;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.app.util.viewer.util.FieldNavigator;
import ghidra.framework.main.GetDomainObjectTask;
import ghidra.framework.main.OpenVersionedFileDialog;
import ghidra.framework.model.*;
import ghidra.framework.options.*;
@ -60,7 +60,8 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
import help.Help;
import help.HelpService;
@ -1143,7 +1144,7 @@ public class ProgramDiffPlugin extends ProgramPlugin
tool.clearStatusInfo();
JComponent component = dialog.getComponent();
Program dobj = dialog.getDomainObject(ProgramDiffPlugin.this, false);
Program dobj = dialog.getDomainObject(ProgramDiffPlugin.this, true);
if (dobj != null) {
if (openSecondProgram(dobj, component)) {
dialog.close();
@ -1151,9 +1152,6 @@ public class ProgramDiffPlugin extends ProgramPlugin
}
return;
}
displayStatus(component, "Can't Open Selected Program",
"Please select a file, not a folder.", OptionDialog.INFORMATION_MESSAGE);
});
dialog.showComponent();
}
@ -1531,17 +1529,20 @@ public class ProgramDiffPlugin extends ProgramPlugin
}
private boolean openSecondProgram(DomainFile df) {
if (!Program.class.isAssignableFrom(df.getDomainObjectClass())) {
Msg.error(this, "Failed to launch Diff for non-Program file: " + df.getName());
return false;
}
OpenSecondProgramTask task = new OpenSecondProgramTask(df);
GetDomainObjectTask task =
new GetDomainObjectTask(this, df, DomainFile.DEFAULT_VERSION, true);
new TaskLauncher(task, tool.getToolFrame(), 500);
// block until the task completes
if (!task.wasCanceled()) {
Program newProgram = task.getDiffProgram();
Program newProgram = (Program) task.getDomainObject();
if (newProgram != null) {
return openSecondProgram(newProgram, null);
}
}
return false;
}
@ -1851,71 +1852,6 @@ public class ProgramDiffPlugin extends ProgramPlugin
}
}
private class OpenSecondProgramTask extends Task {
private DomainFile domainFile;
private Program diffProgram;
private TaskMonitor monitor;
OpenSecondProgramTask(DomainFile domainFile) {
super("Opening Program for Diff", true, true, true);
this.domainFile = domainFile;
}
@Override
public void run(TaskMonitor tm) {
this.monitor = tm;
try {
try {
monitor.setMessage("Waiting on program file...");
diffProgram =
(Program) domainFile.getImmutableDomainObject(ProgramDiffPlugin.this,
DomainFile.DEFAULT_VERSION, monitor);
}
catch (VersionException e) {
if (e.isUpgradable()) {
try {
diffProgram =
(Program) domainFile.getReadOnlyDomainObject(ProgramDiffPlugin.this,
DomainFile.DEFAULT_VERSION, monitor);
}
catch (VersionException exc) {
Msg.showError(this, null, "Error Getting Diff Program",
"Getting read only file failed");
}
catch (IOException exc) {
if (!monitor.isCancelled()) {
Msg.showError(this, null, "Error Getting Diff Program",
"Getting read only file failed", exc);
}
}
}
else {
Msg.showError(this, null, "Error Getting Diff Program",
"File cannot be upgraded.");
}
}
catch (IOException e) {
Msg.showError(this, null, "Error Getting Diff Program",
"Getting read only file failed", e);
}
}
catch (CancelledException e) {
// For now do nothing if user cancels
}
monitor.setMessage("");
}
boolean wasCanceled() {
return monitor.isCancelled();
}
Program getDiffProgram() {
return diffProgram;
}
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent event) {
if (secondaryDiffProgram != null && diffDetailsProvider != null) {

View file

@ -20,7 +20,6 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import javax.help.UnsupportedOperationException;
import javax.swing.Icon;
import generic.theme.GIcon;
@ -45,9 +44,6 @@ import ghidra.util.task.TaskMonitor;
*/
public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBContentHandler<T> {
// TODO: Need to improve by making this meta data on file instead of database content.
// Metadata use would eliminate need for DB but we lack support for non-DB files.
public static final String URL_METADATA_KEY = "link.url";
// 16x16 link icon where link is placed in lower-left corner
@ -76,15 +72,29 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
}
}
@SuppressWarnings("unchecked")
@Override
public final T getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade,
Object consumer, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
if (!okToUpgrade) {
throw new IllegalArgumentException("okToUpgrade must be true");
throw new UnsupportedOperationException("okToUpgrade must be true for link-file");
}
return getObject(item, version, consumer, monitor, false);
}
@Override
public T getImmutableObject(FolderItem item, Object consumer, int version, int minChangeVersion,
TaskMonitor monitor) throws IOException, CancelledException, VersionException {
if (minChangeVersion != -1) {
throw new UnsupportedOperationException("minChangeVersion must be -1 for link-file");
}
return getObject(item, version, consumer, monitor, true);
}
@SuppressWarnings("unchecked")
private T getObject(FolderItem item, int version, Object consumer, TaskMonitor monitor,
boolean immutable)
throws IOException, VersionException, CancelledException {
URL url = getURL(item);
@ -112,10 +122,11 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
DomainFile linkedFile = (DomainFile) content;
if (!getDomainObjectClass().isAssignableFrom(linkedFile.getDomainObjectClass())) {
throw new BadLinkException(
"Excepted " + getDomainObjectClass() + " but linked to " +
"Expected " + getDomainObjectClass() + " but linked to " +
linkedFile.getDomainObjectClass());
}
return (T) linkedFile.getReadOnlyDomainObject(consumer, version, monitor);
return immutable ? (T) linkedFile.getImmutableDomainObject(consumer, version, monitor)
: (T) linkedFile.getReadOnlyDomainObject(consumer, version, monitor);
}
finally {
if (content != null) {
@ -128,16 +139,8 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
public final T getDomainObject(FolderItem item, FileSystem userfs, long checkoutId,
boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException {
// Always upgrade if needed for read-only object
return getReadOnlyObject(item, DomainFile.DEFAULT_VERSION, true, consumer, monitor);
}
@Override
public T getImmutableObject(FolderItem item, Object consumer, int version, int minChangeVersion,
TaskMonitor monitor) throws IOException, CancelledException, VersionException {
//throw new UnsupportedOperationException("link-file does not support getImmutableObject");
// See GP-2903
return getReadOnlyObject(item, version, true, consumer, monitor);
// getReadOnlyObject or getImmutableObject should be used
throw new UnsupportedOperationException("link-file does not support getDomainObject");
}
@Override

View file

@ -0,0 +1,129 @@
/* ###
* 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;
import java.io.IOException;
import docking.widgets.OptionDialog;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.util.VersionExceptionHandler;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* A modal task that gets a domain object for a specified version.
* Object is either open read-only or immutable.
*
* NOTE: This task is not intended to open a domain file for modification and saving back
* to a project.
*
* A file open for read-only use will be upgraded if needed and is possible. Once open it is
* important that the specified consumer be released from the domain object when done using
* the open object (see {@link DomainObject#release(Object)}).
*/
public class GetDomainObjectTask extends Task {
private Object consumer;
private DomainFile domainFile;
private int versionNumber;
private boolean immutable;
private DomainObject versionedObj;
/**
* Construct task open specified domainFile read only.
* An upgrade is performed if needed and is possible.
* @param consumer consumer of the domain object
* @param domainFile domain file
* @param versionNumber version
*/
public GetDomainObjectTask(Object consumer, DomainFile domainFile, int versionNumber) {
this(consumer, domainFile, versionNumber, false);
}
/**
* Construct task open specified domainFile read only or immutable. Immutable mode should not
* be used for content that will be modified.
* If read-only an upgrade is performed if needed, if immutable the user will be prompted
* if an upgrade should be performed if possible in which case it will open read-only.
* @param consumer consumer of the domain object
* @param domainFile domain file
* @param versionNumber version
* @param immutable true if the object should be open immutable, else read-only.
*/
public GetDomainObjectTask(Object consumer, DomainFile domainFile, int versionNumber,
boolean immutable) {
super("Get Versioned Domain Object", true, false, true);
this.consumer = consumer;
this.domainFile = domainFile;
this.versionNumber = versionNumber;
this.immutable = immutable;
}
@Override
public void run(TaskMonitor monitor) {
String contentType = domainFile.getContentType();
try {
monitor.setMessage("Getting Version " + versionNumber + " for " + domainFile.getName());
if (immutable) {
versionedObj =
domainFile.getImmutableDomainObject(consumer, versionNumber, monitor);
}
else {
// Upgrade will be performed if required
versionedObj = domainFile.getReadOnlyDomainObject(consumer, versionNumber, monitor);
}
}
catch (CancelledException e) {
// ignore
}
catch (IOException e) {
ClientUtil.handleException(AppInfo.getActiveProject().getRepository(), e,
contentType + " Open", null);
} catch (VersionException e) {
if (immutable && e.isUpgradable()) {
String detailMessage =
e.getDetailMessage() == null ? "" : "\n" + e.getDetailMessage();
String title = "Upgrade " + contentType + " Data? " + domainFile.getName();
String message = "The " + contentType + " file you are attempting to open" +
" is an older version." + detailMessage + "\n \n" +
"Would you like to Upgrade it now?";
int rc = OptionDialog.showOptionDialog(null, title, message, "Upgrade",
OptionDialog.QUESTION_MESSAGE);
if (rc == OptionDialog.OPTION_ONE) {
// try again as read-only
immutable = false;
run(monitor);
}
return;
}
VersionExceptionHandler.showVersionError(null, domainFile.getName(),
domainFile.getContentType(), contentType + " Open", e);
}
}
/**
* Return the domain object instance.
* @return domain object which was opened or null if task cancelled or failed
*/
public DomainObject getDomainObject() {
return versionedObj;
}
}

View file

@ -1,104 +0,0 @@
/* ###
* 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.
* 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;
import java.io.IOException;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* A modal task that gets a domain object for a specific version.
*
*
*/
public class GetVersionedObjectTask extends Task {
private Object consumer;
private DomainFile domainFile;
private int versionNumber;
private DomainObject versionedObj;
/**
* Constructor; task will get a read only domain object
* @param consumer consumer of the domain object
* @param domainFile domain file
* @param versionNumber version
*/
public GetVersionedObjectTask(Object consumer, DomainFile domainFile,
int versionNumber) {
this(consumer, domainFile, versionNumber, true);
}
/**
* Constructor
* @param consumer consumer of the domain object
* @param domainFile domain file
* @param versionNumber version
* @param readOnly true if the object should be read only versus
* immutable
*/
public GetVersionedObjectTask(Object consumer, DomainFile domainFile,
int versionNumber, boolean readOnly) {
super("Get Versioned Domain Object", true, false, true);
this.consumer = consumer;
this.domainFile = domainFile;
this.versionNumber = versionNumber;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {
monitor.setMessage("Getting Version " + versionNumber +
" for " + domainFile.getName());
versionedObj =
domainFile.getReadOnlyDomainObject(consumer, versionNumber,
monitor);
}catch (CancelledException e) {
}catch (IOException e) {
if (domainFile.isInWritableProject()) {
ClientUtil.handleException(AppInfo.getActiveProject().getRepository(), e,
"Get Versioned Object", null);
}
else {
Msg.showError(this, null,
"Error Getting Versioned Object", "Could not get version " + versionNumber +
" for " + domainFile.getName() + ": " + e, e);
}
} catch (VersionException e) {
Msg.showError(this,
null,
"Error Getting Versioned Object", "Could not get version " + versionNumber +
" for " + domainFile.getName() + ": " + e);
}
}
/**
* Return the versioned domain object.
*/
public DomainObject getVersionedObject() {
return versionedObj;
}
}

View file

@ -370,9 +370,9 @@ class ToolButton extends EmptyBorderButton implements Draggable, Droppable {
}
private DomainObject getVersionedObject(DomainFile file, int versionNumber) {
GetVersionedObjectTask task = new GetVersionedObjectTask(this, file, versionNumber);
GetDomainObjectTask task = new GetDomainObjectTask(this, file, versionNumber);
plugin.getTool().execute(task, 250);
return task.getVersionedObject();
return task.getDomainObject();
}
//==================================================================================================

View file

@ -22,7 +22,7 @@ import java.io.IOException;
import docking.widgets.tree.GTreeNode;
import ghidra.app.util.FileOpenDataFlavorHandler;
import ghidra.framework.client.*;
import ghidra.framework.main.GetVersionedObjectTask;
import ghidra.framework.main.GetDomainObjectTask;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.task.TaskLauncher;
@ -35,17 +35,20 @@ public final class LocalVersionInfoHandler
VersionInfo info = (VersionInfo) obj;
DomainFile file = tool.getProject().getProjectData().getFile(info.getDomainFilePath());
GetVersionedObjectTask task =
new GetVersionedObjectTask(this, file, info.getVersionNumber());
GetDomainObjectTask task =
new GetDomainObjectTask(this, file, info.getVersionNumber());
tool.execute(task, 250);
DomainObject versionedObj = task.getVersionedObject();
DomainObject versionedObj = task.getDomainObject();
if (versionedObj != null) {
try {
DomainFile vfile = versionedObj.getDomainFile();
tool.acceptDomainFiles(new DomainFile[] { vfile });
}
finally {
versionedObj.release(this);
}
}
}
@Override
public void handle(PluginTool tool, DataTree dataTree, GTreeNode destinationNode,

View file

@ -38,7 +38,7 @@ import docking.widgets.OptionDialog;
import docking.widgets.table.*;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.main.GetVersionedObjectTask;
import ghidra.framework.main.GetDomainObjectTask;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.store.ItemCheckoutStatus;
@ -143,31 +143,36 @@ public class VersionHistoryPanel extends JPanel implements Draggable {
}
/**
* Get the domain object for the selected version.
* @param consumer the consumer
* @param readOnly true if read only
* @return null if there is no selection
* Get the selected {@link Version}.
* @return selected {@link Version} or null if no selection
*/
public DomainObject getSelectedVersion(Object consumer, boolean readOnly) {
public Version getSelectedVersion() {
int row = table.getSelectedRow();
if (row >= 0) {
Version version = tableModel.getVersionAt(row);
return getVersionedObject(consumer, version.getVersion(), readOnly);
return tableModel.getVersionAt(row);
}
return null;
}
/**
* Determine if a version selection has been made.
* @return true if version selection has been made, esle false
*/
public boolean isVersionSelected() {
return !table.getSelectionModel().isSelectionEmpty();
}
/**
* Get the selected version number or {@link DomainFile#DEFAULT_VERSION} if no selection.
* @return selected version number
*/
public int getSelectedVersionNumber() {
int row = table.getSelectedRow();
if (row >= 0) {
Version version = tableModel.getVersionAt(row);
return version.getVersion();
}
return -1;
return DomainFile.DEFAULT_VERSION;
}
@Override
@ -260,11 +265,11 @@ public class VersionHistoryPanel extends JPanel implements Draggable {
dragSource.createDefaultDragGestureRecognizer(table, dragAction, dragGestureAdapter);
}
private DomainObject getVersionedObject(Object consumer, int versionNumber, boolean readOnly) {
GetVersionedObjectTask task =
new GetVersionedObjectTask(consumer, domainFile, versionNumber, readOnly);
private DomainObject getVersionedObject(Object consumer, int versionNumber, boolean immutable) {
GetDomainObjectTask task =
new GetDomainObjectTask(consumer, domainFile, versionNumber, immutable);
tool.execute(task, 1000);
return task.getVersionedObject();
return task.getDomainObject();
}
private void delete() {
@ -337,15 +342,19 @@ public class VersionHistoryPanel extends JPanel implements Draggable {
Version version = tableModel.getVersionAt(row);
DomainObject versionedObj = getVersionedObject(this, version.getVersion(), true);
if (versionedObj != null) {
try {
if (toolName != null) {
tool.getToolServices().launchTool(toolName, versionedObj.getDomainFile());
}
else {
tool.getToolServices().launchDefaultTool(versionedObj.getDomainFile());
}
}
finally {
versionedObj.release(this);
}
}
}
private void open() {
openWith(null);

View file

@ -187,6 +187,9 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* 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

View file

@ -145,7 +145,6 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
}
catch (Throwable t) {
Msg.error(this, "getReadOnlyObject failed", t);
t.printStackTrace();
String msg = t.getMessage();
if (msg == null) {
msg = t.toString();