GP-4390 Collapse DomainObject Undoable interfaces and refactor Command processing.

This commit is contained in:
ghidra1 2024-03-08 12:49:50 -05:00
parent 136b933af2
commit 445494ba25
214 changed files with 2862 additions and 3813 deletions

View file

@ -16,6 +16,7 @@
package ghidra.framework.cmd;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.task.TaskMonitor;
/**
@ -26,8 +27,10 @@ import ghidra.util.task.TaskMonitor;
*
* <p>This allows commands to make changes in the background so that the GUI is not frozen and the
* user can still interact with the GUI.
*
* @param <T> {@link DomainObject} implementation interface
*/
public abstract class BackgroundCommand implements Command {
public abstract class BackgroundCommand<T extends DomainObject> implements Command<T> {
private String name;
private boolean hasProgress;
@ -47,7 +50,7 @@ public abstract class BackgroundCommand implements Command {
}
@Override
public final boolean applyTo(DomainObject obj) {
public final boolean applyTo(T obj) {
return applyTo(obj, TaskMonitor.DUMMY);
}
@ -59,7 +62,7 @@ public abstract class BackgroundCommand implements Command {
* @param monitor monitor to show progress of the command
* @return true if the command applied successfully
*/
public abstract boolean applyTo(DomainObject obj, TaskMonitor monitor);
public abstract boolean applyTo(T obj, TaskMonitor monitor);
// TODO: This should really throw CancelledException when canceled
@ -126,4 +129,8 @@ public abstract class BackgroundCommand implements Command {
public String toString() {
return getName();
}
public void run(PluginTool tool, T obj) {
tool.executeBackgroundCommand(this, obj);
}
}

View file

@ -1,29 +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.cmd;
/**
* Listener that is notified when a BackgroundCommand completes.
*/
public interface BackgroundCommandListener {
/**
* Notification that the given BackgroundCommand has completed.
* @param cmd background command that has completed
*/
public void commandCompleted(BackgroundCommand cmd);
}

View file

@ -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,8 +20,9 @@ import ghidra.framework.model.DomainObject;
/**
* Interface to define a change made to a domain object.
*
* @param <T> {@link DomainObject} implementation interface
*/
public interface Command {
public interface Command<T extends DomainObject> {
/**
* Applies the command to the given domain object.
@ -31,8 +31,8 @@ public interface Command {
*
* @return true if the command applied successfully
*/
public boolean applyTo(DomainObject obj);
public boolean applyTo(T obj);
/**
* Returns the status message indicating the status of the command.
*
@ -40,7 +40,7 @@ public interface Command {
* was successful
*/
public String getStatusMsg();
/**
* Returns the name of this command.
*

View file

@ -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.
@ -16,18 +15,19 @@
*/
package ghidra.framework.cmd;
import java.util.ArrayList;
import ghidra.framework.model.DomainObject;
import ghidra.util.task.TaskMonitor;
import java.util.ArrayList;
/**
* Compound command to handle multiple background commands.
*
* @param <T> {@link DomainObject} implementation interface
*/
public class CompoundBackgroundCommand extends BackgroundCommand {
public class CompoundBackgroundCommand<T extends DomainObject> extends BackgroundCommand<T> {
private ArrayList<BackgroundCommand> bkgroundCmdList;
private ArrayList<Command> cmdList;
private ArrayList<Command<T>> cmdList;
/**
* Constructor
@ -38,30 +38,25 @@ public class CompoundBackgroundCommand extends BackgroundCommand {
*/
public CompoundBackgroundCommand(String name, boolean modal, boolean canCancel) {
super(name, false, canCancel, modal);
bkgroundCmdList = new ArrayList<BackgroundCommand>();
cmdList = new ArrayList<Command>();
cmdList = new ArrayList<>();
}
/* (non-Javadoc)
* @see ghidra.framework.cmd.BackgroundCommand#applyTo(ghidra.framework.model.DomainObject, ghidra.util.task.TaskMonitor)
*/
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
for (int i = 0; i < bkgroundCmdList.size(); i++) {
BackgroundCommand cmd = bkgroundCmdList.get(i);
if (!cmd.applyTo(obj, monitor)) {
setStatusMsg(cmd.getStatusMsg());
return false;
}
}
for (int i = 0; i < cmdList.size(); i++) {
public boolean applyTo(T obj, TaskMonitor monitor) {
// Run commands in the order they were added
for (Command<T> cmd : cmdList) {
if (monitor.isCancelled()) {
setStatusMsg("Cancelled");
return false;
}
Command cmd = cmdList.get(i);
if (!cmd.applyTo(obj)) {
boolean success;
if (cmd instanceof BackgroundCommand<T> bcmd) {
success = bcmd.applyTo(obj, monitor);
}
else {
success = cmd.applyTo(obj);
}
if (!success) {
setStatusMsg(cmd.getStatusMsg());
return false;
}
@ -69,32 +64,27 @@ public class CompoundBackgroundCommand extends BackgroundCommand {
return true;
}
/**
* Add a background command to this compound background command.
*/
public void add(BackgroundCommand cmd) {
bkgroundCmdList.add(cmd);
}
/**
* Add a command to this compound background command.
* @param cmd command to be added
*/
public void add(Command cmd) {
public void add(Command<T> cmd) {
cmdList.add(cmd);
}
/**
* Get the number of background commands in this compound background
* command.
* @return the number of commands
*/
public int size() {
return bkgroundCmdList.size();
return cmdList.size();
}
/**
* @return true if no sub-commands have been added
*/
public boolean isEmpty() {
return bkgroundCmdList.isEmpty();
return cmdList.isEmpty();
}
}

View file

@ -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.
@ -26,30 +25,26 @@ import java.util.ArrayList;
* Multiple commands may be added to this one so that multiple changes can be
* applied to the domain object as unit.
*
*
* @param <T> {@link DomainObject} implementation interface
*/
public class CompoundCmd implements Command {
private ArrayList<Command> cmds;
public class CompoundCmd<T extends DomainObject> implements Command<T> {
private ArrayList<Command<T>> cmds;
private String statusMsg;
private String name;
/**
* Constructor for CompoundCmd.
*
* @param name the name of the command
*/
public CompoundCmd(String name) {
cmds = new ArrayList<Command>();
cmds = new ArrayList<>();
this.name = name;
}
/*
* @see ghidra.framework.cmd.Command#applyTo(ghidra.framework.model.DomainObject)
*/
public boolean applyTo(DomainObject obj) {
for(int i = 0;i<cmds.size();i++) {
Command cmd = cmds.get(i);
@Override
public boolean applyTo(T obj) {
for (Command<T> cmd : cmds) {
if (!cmd.applyTo(obj)) {
statusMsg = cmd.getStatusMsg();
return false;
@ -58,26 +53,22 @@ public class CompoundCmd implements Command {
return true;
}
/*
* @see ghidra.framework.cmd.Command#getStatusMsg()
*/
@Override
public String getStatusMsg() {
return statusMsg;
}
/*
* @see ghidra.framework.cmd.Command#getName()
*/
@Override
public String getName() {
return name;
}
/**
* Add the given command to this command.
*
* @param cmd command to add to this command
*/
public void add(Command cmd) {
public void add(Command<T> cmd) {
cmds.add(cmd);
}
@ -89,6 +80,5 @@ public class CompoundCmd implements Command {
public int size() {
return cmds.size();
}
}

View file

@ -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.
@ -16,12 +15,21 @@
*/
package ghidra.framework.cmd;
public abstract class MergeableBackgroundCommand extends BackgroundCommand {
import ghidra.framework.model.DomainObject;
public abstract class MergeableBackgroundCommand<T extends DomainObject>
extends BackgroundCommand<T> {
public MergeableBackgroundCommand(String name, boolean hasProgress, boolean canCancel,
boolean isModal) {
super(name, hasProgress, canCancel, isModal);
}
/** Merges the properties of the two commands */
public abstract MergeableBackgroundCommand mergeCommands(MergeableBackgroundCommand command);
/**
* Merges the properties of the two commands
* @param command command to be merged with this one
* @return resulting merged command
*/
public abstract MergeableBackgroundCommand<T> mergeCommands(
MergeableBackgroundCommand<T> command);
}

View file

@ -65,7 +65,7 @@ import ghidra.util.task.TaskMonitor;
* </pre>
*/
public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
implements UndoableDomainObject, ErrorHandler, DBConstants {
implements ErrorHandler, DBConstants {
protected static final int NUM_UNDOS = 50;

View file

@ -352,31 +352,31 @@ class FileActionManager {
* all domain objects.
*/
private DomainObject[] lockDomainObjects(List<DomainFile> files) {
DomainObject[] objs = new DomainObject[files.size()];
DomainObject[] domainObjects = new DomainObject[files.size()];
int lastIndex = 0;
boolean locked = true;
while (lastIndex < files.size()) {
try {
objs[lastIndex] = files.get(lastIndex).getDomainObject(this, false, false, null);
domainObjects[lastIndex] =
files.get(lastIndex).getDomainObject(this, false, false, null);
}
catch (Throwable t) {
Msg.error(this, "Failed to aqcuire domain object instance", t);
locked = false;
break;
}
if (!objs[lastIndex].lock(null)) {
if (!domainObjects[lastIndex].lock(null)) {
String title = "Exit Ghidra";
StringBuffer buf = new StringBuffer();
UndoableDomainObject udo = (UndoableDomainObject) objs[lastIndex];
DomainObject d = domainObjects[lastIndex];
buf.append("The File " + files.get(lastIndex).getPathname() +
" is currently being modified by the\n");
buf.append("the following actions:\n \n");
TransactionInfo t = udo.getCurrentTransactionInfo();
TransactionInfo t = d.getCurrentTransactionInfo();
List<String> list = t.getOpenSubTransactions();
Iterator<String> it = list.iterator();
while (it.hasNext()) {
for (String element : list) {
buf.append("\n ");
buf.append(it.next());
buf.append(element);
}
buf.append("\n \n");
buf.append(
@ -391,22 +391,22 @@ class FileActionManager {
if (result == OptionDialog.CANCEL_OPTION) {
locked = false;
objs[lastIndex].release(this);
domainObjects[lastIndex].release(this);
break;
}
udo.forceLock(true, null);
d.forceLock(true, null);
}
++lastIndex;
}
if (!locked) {
//skip the last one that could not be locked...
for (int i = 0; i < lastIndex; i++) {
objs[i].unlock();
objs[i].release(this);
domainObjects[i].unlock();
domainObjects[i].release(this);
}
return null;
}
return objs;
return domainObjects;
}
/**

View file

@ -20,11 +20,16 @@ import java.io.IOException;
import java.util.List;
import java.util.Map;
import db.TerminatedTransactionException;
import db.Transaction;
import ghidra.framework.data.DomainObjectFileListener;
import ghidra.framework.options.Options;
import ghidra.framework.store.LockException;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import utility.function.ExceptionalCallback;
import utility.function.ExceptionalSupplier;
/**
* <CODE>DomainObject</CODE> is the interface that must be supported by
@ -32,6 +37,15 @@ import ghidra.util.task.TaskMonitor;
* association with a <CODE>DomainFile</CODE>. A <CODE>DomainObject</CODE> that
* has never been saved will have a null <CODE>DomainFile</CODE>.
* <P>
* Supports transactions and the ability to undo/redo changes made within a stack of
* recent transactions. Each transactions may contain many sub-transactions which
* reflect concurrent changes to the domain object. If any sub-transaction fails to commit,
* all concurrent sub-transaction changes will be rolled-back.
* <P>
* NOTE: A <i>transaction</i> must be started in order
* to make any change to this domain object - failure to do so will result in a
* IOException.
* <P>
* Note: Previously (before 11.1), domain object change event types were defined in this file as
* integer constants. Event ids have since been converted to enum types. The defines in this file
* have been converted to point to the new enum values to make it easier to convert to this new way
@ -376,4 +390,226 @@ public interface DomainObject {
* @return a long value that is incremented for every change to the program.
*/
public long getModificationNumber();
/**
* Open new transaction. This should generally be done with a try-with-resources block:
* <pre>
* try (Transaction tx = dobj.openTransaction(description)) {
* // ... Do something
* }
* </pre>
*
* @param description a short description of the changes to be made.
* @return transaction object
* @throws IllegalStateException if this {@link DomainObject} has already been closed.
*/
public Transaction openTransaction(String description) throws IllegalStateException;
/**
* Performs the given callback inside of a transaction. Use this method in place of the more
* verbose try/catch/finally semantics.
* <p>
* <pre>
* program.withTransaction("My Description", () -> {
* // ... Do something
* });
* </pre>
*
* <p>
* Note: the transaction created by this method will always be committed when the call is
* finished. If you need the ability to abort transactions, then you need to use the other
* methods on this interface.
*
* @param description brief description of transaction
* @param callback the callback that will be called inside of a transaction
* @throws E any exception that may be thrown in the given callback
*/
public default <E extends Exception> void withTransaction(String description,
ExceptionalCallback<E> callback) throws E {
int id = startTransaction(description);
try {
callback.call();
}
finally {
endTransaction(id, true);
}
}
/**
* Calls the given supplier inside of a transaction. Use this method in place of the more
* verbose try/catch/finally semantics.
* <p>
* <pre>
* program.withTransaction("My Description", () -> {
* // ... Do something
* return result;
* });
* </pre>
* <p>
* If you do not need to supply a result, then use
* {@link #withTransaction(String, ExceptionalCallback)} instead.
*
* @param <E> the exception that may be thrown from this method
* @param <T> the type of result returned by the supplier
* @param description brief description of transaction
* @param supplier the supplier that will be called inside of a transaction
* @return the result returned by the supplier
* @throws E any exception that may be thrown in the given callback
*/
public default <E extends Exception, T> T withTransaction(String description,
ExceptionalSupplier<T, E> supplier) throws E {
T t = null;
boolean success = false;
int id = startTransaction(description);
try {
t = supplier.get();
success = true;
}
finally {
endTransaction(id, success);
}
return t;
}
/**
* Start a new transaction in order to make changes to this domain object.
* All changes must be made in the context of a transaction.
* If a transaction is already in progress, a sub-transaction
* of the current transaction will be returned.
* @param description brief description of transaction
* @return transaction ID
* @throws DomainObjectLockedException the domain object is currently locked
* @throws TerminatedTransactionException an existing transaction which has not yet ended was terminated early.
* Sub-transactions are not permitted until the terminated transaction ends.
*/
public int startTransaction(String description);
/**
* Start a new transaction in order to make changes to this domain object.
* All changes must be made in the context of a transaction.
* If a transaction is already in progress, a sub-transaction
* of the current transaction will be returned.
* @param description brief description of transaction
* @param listener listener to be notified if the transaction is aborted.
* @return transaction ID
* @throws DomainObjectLockedException the domain object is currently locked
* @throws TerminatedTransactionException an existing transaction which has not yet ended was terminated early.
* Sub-transactions are not permitted until the terminated transaction ends.
*/
public int startTransaction(String description, AbortedTransactionListener listener);
/**
* Terminate the specified transaction for this domain object.
* @param transactionID transaction ID obtained from startTransaction method
* @param commit if true the changes made in this transaction will be marked for commit,
* if false this and any concurrent transaction will be rolled-back.
*/
public void endTransaction(int transactionID, boolean commit);
/**
* Returns the current transaction info
* @return the current transaction info
*/
public TransactionInfo getCurrentTransactionInfo();
/**
* Returns true if the last transaction was terminated from the action that started it.
* @return true if the last transaction was terminated from the action that started it.
*/
public boolean hasTerminatedTransaction();
/**
* Return array of all domain objects synchronized with a
* shared transaction manager.
* @return returns array of synchronized domain objects or
* null if this domain object is not synchronized with others.
*/
public DomainObject[] getSynchronizedDomainObjects();
/**
* Synchronize the specified domain object with this domain object
* using a shared transaction manager. If either or both is already shared,
* a transition to a single shared transaction manager will be
* performed.
* @param domainObj the domain object
* @throws LockException if lock or open transaction is active on either
* this or the specified domain object
*/
public void addSynchronizedDomainObject(DomainObject domainObj) throws LockException;
/**
* Remove this domain object from a shared transaction manager. If
* this object has not been synchronized with others via a shared
* transaction manager, this method will have no affect.
* @throws LockException if lock or open transaction is active
*/
public void releaseSynchronizedDomainObject() throws LockException;
/**
* Returns true if there is a previous state to "undo" to.
*/
boolean canUndo();
/**
* Returns true if there is a later state to "redo" to.
*/
boolean canRedo();
/**
* Clear all undoable/redoable transactions
*/
public void clearUndo();
/**
* Returns to the previous state. Normally, this will cause the current state
* to appear on the "redo" stack. This method will do nothing if there are
* no previous states to "undo".
* @throws IOException if an IO error occurs
*/
void undo() throws IOException;
/**
* Returns to a latter state that exists because of an undo. Normally, this
* will cause the current state to appear on the "undo" stack. This method
* will do nothing if there are no latter states to "redo".
* @throws IOException if an IO error occurs
*/
void redo() throws IOException;
/**
* Returns a description of the change that would be "undone".
* @return a description of the change that would be "undone".
*/
public String getUndoName();
/**
* Returns a description of the change that would be "redone".
* @return a description of the change that would be "redone".
*/
public String getRedoName();
/**
* Returns a list of the names of all current undo transactions
* @return a list of the names of all current undo transactions
*/
public List<String> getAllUndoNames();
/**
* Returns a list of the names of all current redo transactions
* @return a list of the names of all current redo transactions
*/
public List<String> getAllRedoNames();
/**
* Adds the given transaction listener to this domain object
* @param listener the new transaction listener to add
*/
public void addTransactionListener(TransactionListener listener);
/**
* Removes the given transaction listener from this domain object.
* @param listener the transaction listener to remove
*/
public void removeTransactionListener(TransactionListener listener);
}

View file

@ -1,96 +0,0 @@
/* ###
* 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.model;
import java.io.IOException;
import java.util.List;
/**
* Objects that implement Undoable have the ability to "remember" some number
* of stable states that are created as operations are performed upon them. The
* object then provides methods for "undoing" to a previous state or "redoing" to
* a later state.
*/
public interface Undoable {
/**
* Returns true if there is a previous state to "undo" to.
*/
boolean canUndo();
/**
* Returns true if there is a later state to "redo" to.
*/
boolean canRedo();
/**
* Clear all undoable/redoable transactions
*/
public void clearUndo();
/**
* Returns to the previous state. Normally, this will cause the current state
* to appear on the "redo" stack. This method will do nothing if there are
* no previous states to "undo".
* @throws IOException if an IO error occurs
*/
void undo() throws IOException;
/**
* Returns to a latter state that exists because of an undo. Normally, this
* will cause the current state to appear on the "undo" stack. This method
* will do nothing if there are no latter states to "redo".
* @throws IOException if an IO error occurs
*/
void redo() throws IOException;
/**
* Returns a description of the change that would be "undone".
* @return a description of the change that would be "undone".
*/
public String getUndoName();
/**
* Returns a description of the change that would be "redone".
* @return a description of the change that would be "redone".
*/
public String getRedoName();
/**
* Returns a list of the names of all current undo transactions
* @return a list of the names of all current undo transactions
*/
public List<String> getAllUndoNames();
/**
* Returns a list of the names of all current redo transactions
* @return a list of the names of all current redo transactions
*/
public List<String> getAllRedoNames();
/**
* Adds the given transaction listener to this domain object
* @param listener the new transaction listener to add
*/
public void addTransactionListener(TransactionListener listener);
/**
* Removes the given transaction listener from this domain object.
* @param listener the transaction listener to remove
*/
public void removeTransactionListener(TransactionListener listener);
}

View file

@ -1,193 +0,0 @@
/* ###
* 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.model;
import db.TerminatedTransactionException;
import db.Transaction;
import ghidra.framework.store.LockException;
import utility.function.ExceptionalCallback;
import utility.function.ExceptionalSupplier;
/**
* <code>UndoableDomainObject</code> extends a domain object to provide transaction
* support and the ability to undo and redo changes made within a stack of
* recent transactions. Each transactions may contain many sub-transactions which
* reflect concurrent changes to the domain object. If any sub-transaction fails to commit,
* all concurrent sub-transaction changes will be rolled-back.
* <P>
* NOTE: A <i>transaction</i> must be started in order
* to make any change to this domain object - failure to do so will result in a
* IOException.
* @see #startTransaction(String)
* @see #endTransaction(int, boolean)
*/
public interface UndoableDomainObject extends DomainObject, Undoable {
/**
* Open new transaction. This should generally be done with a try-with-resources block:
* <pre>
* try (Transaction tx = dobj.openTransaction(description)) {
* // ... Do something
* }
* </pre>
*
* @param description a short description of the changes to be made.
* @return transaction object
* @throws IllegalStateException if this {@link DomainObject} has already been closed.
*/
public Transaction openTransaction(String description) throws IllegalStateException;
/**
* Performs the given callback inside of a transaction. Use this method in place of the more
* verbose try/catch/finally semantics.
* <p>
* <pre>
* program.withTransaction("My Description", () -> {
* // ... Do something
* });
* </pre>
*
* <p>
* Note: the transaction created by this method will always be committed when the call is
* finished. If you need the ability to abort transactions, then you need to use the other
* methods on this interface.
*
* @param description brief description of transaction
* @param callback the callback that will be called inside of a transaction
* @throws E any exception that may be thrown in the given callback
*/
public default <E extends Exception> void withTransaction(String description,
ExceptionalCallback<E> callback) throws E {
int id = startTransaction(description);
try {
callback.call();
}
finally {
endTransaction(id, true);
}
}
/**
* Calls the given supplier inside of a transaction. Use this method in place of the more
* verbose try/catch/finally semantics.
* <p>
* <pre>
* program.withTransaction("My Description", () -> {
* // ... Do something
* return result;
* });
* </pre>
* <p>
* If you do not need to supply a result, then use
* {@link #withTransaction(String, ExceptionalCallback)} instead.
*
* @param <E> the exception that may be thrown from this method
* @param <T> the type of result returned by the supplier
* @param description brief description of transaction
* @param supplier the supplier that will be called inside of a transaction
* @return the result returned by the supplier
* @throws E any exception that may be thrown in the given callback
*/
public default <E extends Exception, T> T withTransaction(String description,
ExceptionalSupplier<T, E> supplier) throws E {
T t = null;
boolean success = false;
int id = startTransaction(description);
try {
t = supplier.get();
success = true;
}
finally {
endTransaction(id, success);
}
return t;
}
/**
* Start a new transaction in order to make changes to this domain object.
* All changes must be made in the context of a transaction.
* If a transaction is already in progress, a sub-transaction
* of the current transaction will be returned.
* @param description brief description of transaction
* @return transaction ID
* @throws DomainObjectLockedException the domain object is currently locked
* @throws TerminatedTransactionException an existing transaction which has not yet ended was terminated early.
* Sub-transactions are not permitted until the terminated transaction ends.
*/
public int startTransaction(String description);
/**
* Start a new transaction in order to make changes to this domain object.
* All changes must be made in the context of a transaction.
* If a transaction is already in progress, a sub-transaction
* of the current transaction will be returned.
* @param description brief description of transaction
* @param listener listener to be notified if the transaction is aborted.
* @return transaction ID
* @throws DomainObjectLockedException the domain object is currently locked
* @throws TerminatedTransactionException an existing transaction which has not yet ended was terminated early.
* Sub-transactions are not permitted until the terminated transaction ends.
*/
public int startTransaction(String description, AbortedTransactionListener listener);
/**
* Terminate the specified transaction for this domain object.
* @param transactionID transaction ID obtained from startTransaction method
* @param commit if true the changes made in this transaction will be marked for commit,
* if false this and any concurrent transaction will be rolled-back.
*/
public void endTransaction(int transactionID, boolean commit);
/**
* Returns the current transaction info
* @return the current transaction info
*/
public TransactionInfo getCurrentTransactionInfo();
/**
* Returns true if the last transaction was terminated from the action that started it.
* @return true if the last transaction was terminated from the action that started it.
*/
public boolean hasTerminatedTransaction();
/**
* Return array of all domain objects synchronized with a
* shared transaction manager.
* @return returns array of synchronized domain objects or
* null if this domain object is not synchronized with others.
*/
public DomainObject[] getSynchronizedDomainObjects();
/**
* Synchronize the specified domain object with this domain object
* using a shared transaction manager. If either or both is already shared,
* a transition to a single shared transaction manager will be
* performed.
* @param domainObj the domain object
* @throws LockException if lock or open transaction is active on either
* this or the specified domain object
*/
public void addSynchronizedDomainObject(DomainObject domainObj) throws LockException;
/**
* Remove this domain object from a shared transaction manager. If
* this object has not been synchronized with others via a shared
* transaction manager, this method will have no affect.
* @throws LockException if lock or open transaction is active
*/
public void releaseSynchronizedDomainObject() throws LockException;
}

View file

@ -25,6 +25,7 @@ import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import javax.swing.*;
@ -501,8 +502,7 @@ public abstract class PluginTool extends AbstractDockingTool {
pluginMgr.close();
if (project != null) {
if (project.getToolManager() != null) {
project.getToolManager()
.disconnectTool(this);
project.getToolManager().disconnectTool(this);
}
}
@ -692,6 +692,39 @@ public abstract class PluginTool extends AbstractDockingTool {
eventMgr.processToolEvent(toolEvent);
}
/**
* Execute the given command in the foreground. Required domain object transaction will be
* started with delayed end to ensure that any follow-on analysis starts prior to transaction
* end.
*
* @param <T> {@link DomainObject} implementation interface
* @param commandName command name to be associated with transaction
* @param domainObject domain object to be modified
* @param f command function callback which should return true on success or false on failure.
* @return result from command function callback
*/
public <T extends DomainObject> boolean execute(String commandName, T domainObject,
Function<T, Boolean> f) {
return taskMgr.execute(commandName, domainObject, f);
}
/**
* Execute the given command in the foreground. Required domain object transaction will be
* started with delayed end to ensure that any follow-on analysis starts prior to transaction
* end.
*
* @param <T> {@link DomainObject} implementation interface
* @param commandName command name to be associated with transaction
* @param domainObject domain object to be modified
* @param r command function runnable
*/
public <T extends DomainObject> void execute(String commandName, T domainObject, Runnable r) {
execute(commandName, domainObject, d -> {
r.run();
return true;
});
}
/**
* Call the applyTo() method on the given command to make some change to
* the domain object; the command is done in the AWT thread, therefore,
@ -701,9 +734,9 @@ public abstract class PluginTool extends AbstractDockingTool {
* @param command command to apply
* @param obj domain object that the command will be applied to
* @return status of the command's applyTo() method
* @see #executeBackgroundCommand(BackgroundCommand, UndoableDomainObject)
* @see #executeBackgroundCommand(BackgroundCommand, DomainObject)
*/
public boolean execute(Command command, DomainObject obj) {
public <T extends DomainObject> boolean execute(Command<T> command, T obj) {
return taskMgr.execute(command, obj);
}
@ -721,8 +754,7 @@ public abstract class PluginTool extends AbstractDockingTool {
*/
public boolean threadIsBackgroundTaskThread() {
ThreadGroup taskGroup = taskMgr.getTaskThreadGroup();
ThreadGroup group = Thread.currentThread()
.getThreadGroup();
ThreadGroup group = Thread.currentThread().getThreadGroup();
while (group != null && group != taskGroup) {
group = group.getParent();
}
@ -738,7 +770,7 @@ public abstract class PluginTool extends AbstractDockingTool {
* AWT Thread)
* @param obj domain object that the command will be applied to
*/
public void executeBackgroundCommand(BackgroundCommand cmd, UndoableDomainObject obj) {
public <T extends DomainObject> void executeBackgroundCommand(BackgroundCommand<T> cmd, T obj) {
taskMgr.executeCommand(cmd, obj);
}
@ -748,7 +780,7 @@ public abstract class PluginTool extends AbstractDockingTool {
* @param cmd background command to submit
* @param obj the domain object to be modified by the command.
*/
public void scheduleFollowOnCommand(BackgroundCommand cmd, UndoableDomainObject obj) {
public <T extends DomainObject> void scheduleFollowOnCommand(BackgroundCommand<T> cmd, T obj) {
taskMgr.scheduleFollowOnCommand(cmd, obj);
}
@ -1332,8 +1364,7 @@ public abstract class PluginTool extends AbstractDockingTool {
* @param height height in pixels
*/
public void setSize(int width, int height) {
winMgr.getMainWindow()
.setSize(new Dimension(width, height));
winMgr.getMainWindow().setSize(new Dimension(width, height));
}
/**
@ -1341,8 +1372,7 @@ public abstract class PluginTool extends AbstractDockingTool {
* @return dimension of this tool's frame
*/
public Dimension getSize() {
return winMgr.getMainWindow()
.getSize();
return winMgr.getMainWindow().getSize();
}
/**
@ -1351,8 +1381,7 @@ public abstract class PluginTool extends AbstractDockingTool {
* @param y screen y coordinate
*/
public void setLocation(int x, int y) {
winMgr.getMainWindow()
.setLocation(x, y);
winMgr.getMainWindow().setLocation(x, y);
}
/**
@ -1360,8 +1389,7 @@ public abstract class PluginTool extends AbstractDockingTool {
* @return location of this tool's frame
*/
public Point getLocation() {
return winMgr.getMainWindow()
.getLocation();
return winMgr.getMainWindow().getLocation();
}
private void updateTitle() {

View file

@ -27,7 +27,6 @@ import ghidra.util.HelpLocation;
public class StandAlonePluginTool extends PluginTool {
private PluginsConfiguration pluginClassManager;
private DockingAction configureToolAction;
private final GenericStandAloneApplication app;
private final String name;

View file

@ -31,11 +31,12 @@ import ghidra.util.task.TaskMonitor;
/**
* A task that executes a command in separate thread, not in the Swing Thread
*/
class BackgroundCommandTask extends Task implements AbortedTransactionListener {
class BackgroundCommandTask<T extends DomainObject> extends Task
implements AbortedTransactionListener {
private BackgroundCommand cmd;
private BackgroundCommand<T> cmd;
private ToolTaskManager taskMgr;
private UndoableDomainObject obj;
private T obj;
private boolean doneQueueProcessing;
@ -46,8 +47,7 @@ class BackgroundCommandTask extends Task implements AbortedTransactionListener {
* @param obj the domain object to be modified by this task.
* @param cmd the background command to invoke.
*/
public BackgroundCommandTask(ToolTaskManager taskMgr, UndoableDomainObject obj,
BackgroundCommand cmd) {
public BackgroundCommandTask(ToolTaskManager taskMgr, T obj, BackgroundCommand<T> cmd) {
super(cmd.getName(), cmd.canCancel(), cmd.hasProgress(), cmd.isModal());
this.cmd = cmd;
this.taskMgr = taskMgr;
@ -58,7 +58,7 @@ class BackgroundCommandTask extends Task implements AbortedTransactionListener {
* Returns the Domain Object associated with this Task
* @return the object
*/
public UndoableDomainObject getDomainObject() {
public T getDomainObject() {
return obj;
}
@ -67,7 +67,7 @@ class BackgroundCommandTask extends Task implements AbortedTransactionListener {
*
* @return background command
*/
public BackgroundCommand getCommand() {
public BackgroundCommand<T> getCommand() {
return cmd;
}

View file

@ -20,12 +20,14 @@ import java.rmi.ConnectException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import ghidra.framework.cmd.*;
import ghidra.framework.model.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectException;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.*;
import ghidra.util.datastruct.PriorityQueue;
@ -44,16 +46,15 @@ public class ToolTaskManager implements Runnable {
private volatile PluginTool tool;
private volatile boolean isExecuting;
private LinkedList<BackgroundCommandTask> tasks = new LinkedList<>();
private Map<UndoableDomainObject, PriorityQueue<BackgroundCommand>> queuedCommandsMap =
private LinkedList<BackgroundCommandTask<?>> tasks = new LinkedList<>();
private Map<DomainObject, PriorityQueue<BackgroundCommand<?>>> queuedCommandsMap =
new HashMap<>();
private Map<UndoableDomainObject, Integer> openForgroundTransactionIDs = new HashMap<>();
private long startQueueTime = 0;
private long startTaskTime = 0;
private Thread taskThread;
private ThreadGroup taskThreadGroup;
private ToolTaskMonitor toolTaskMonitor;
private BackgroundCommandTask currentTask;
private BackgroundCommandTask<?> currentTask;
private TaskDialog modalTaskDialog;
/**
@ -98,7 +99,25 @@ public class ToolTaskManager implements Runnable {
}
/**
* Execute the given command in the foreground
* Execute the given command in the foreground. Required domain object transaction will be
* started with delayed end to ensure that any follow-on analysis starts prior to transaction
* end.
*
* @param <T> {@link DomainObject} implementation interface
* @param commandName command name to be associated with transaction
* @param domainObject domain object to be modified
* @param f command function callback which should return true on success or false on failure.
* @return result from command function callback
*/
public <T extends DomainObject> boolean execute(String commandName, T domainObject,
Function<T, Boolean> f) {
return execute(new SimpleCommand<T>(commandName, f), domainObject);
}
/**
* Execute the given command in the foreground. Required domain object transaction will be
* started with delayed end to ensure that any follow-on analysis starts prior to transaction
* end.
*
* @param cmd command to execute
* @param obj domain object to which the command will be applied
@ -106,7 +125,7 @@ public class ToolTaskManager implements Runnable {
*
* @see Command#applyTo(DomainObject)
*/
public boolean execute(Command cmd, DomainObject obj) {
public <T extends DomainObject> boolean execute(Command<T> cmd, T obj) {
if (tool == null) {
return false; // disposed
}
@ -115,13 +134,7 @@ public class ToolTaskManager implements Runnable {
boolean success = false;
isExecuting = true;
try {
if (obj instanceof UndoableDomainObject) {
UndoableDomainObject undoObj = (UndoableDomainObject) obj;
success = applyCommand(cmd, undoObj);
}
else {
success = cmd.applyTo(obj);
}
success = applyCommand(cmd, obj);
}
finally {
isExecuting = false;
@ -139,20 +152,20 @@ public class ToolTaskManager implements Runnable {
return success;
}
private boolean applyCommand(Command cmd, UndoableDomainObject domainObject) {
private <T extends DomainObject> boolean applyCommand(Command<T> cmd, T domainObject) {
boolean success = false;
boolean error = false;
String cmdName = cmd.getName();
int id = domainObject.startTransaction(cmdName);
int txId = domainObject.startTransaction(cmdName);
try {
try {
success = cmd.applyTo(domainObject);
// TODO Ok, this seems bad--why track the success of the given command, but
// not any of the queued commands? (Are they considered unrelated follow-up
// commands?)
executeQueueCommands(domainObject, cmdName);
// Schedule empty background task to trigger flushEvents and processing of
// resulting queued commands. This is standard behavior when any
// BackgroundCommand completes its execution (see taskCompleted method).
executeCommand(new EmptyBackgroundCommand<T>(cmdName), domainObject);
}
catch (Throwable t) {
error = true;
@ -174,7 +187,7 @@ public class ToolTaskManager implements Runnable {
}
}
finally {
domainObject.endTransaction(id, !error);
domainObject.endTransaction(txId, !error);
}
return success;
@ -186,12 +199,13 @@ public class ToolTaskManager implements Runnable {
* @param cmd background command
* @param obj domain object that supports undo/redo
*/
public synchronized void executeCommand(BackgroundCommand cmd, UndoableDomainObject obj) {
public synchronized <T extends DomainObject> void executeCommand(BackgroundCommand<T> cmd,
T obj) {
if (tool == null) {
return;
}
BackgroundCommandTask task = new BackgroundCommandTask(this, obj, cmd);
BackgroundCommandTask<T> task = new BackgroundCommandTask<>(this, obj, cmd);
tasks.addLast(task);
if (taskThread != null && taskThread.isAlive()) {
@ -202,9 +216,9 @@ public class ToolTaskManager implements Runnable {
taskThread.setPriority(Thread.MIN_PRIORITY + 1);
taskThread.start();
try {
// We will get notified by the task, after it has started the transaction
// TODO: why do we need to wait until the transaction is started?!?
// Wait for background command task to start its transaction and notify us.
// This is done to ensure any preceeding foreground Command transaction
// becomes entangled with the task execution.
wait(1000);
}
catch (InterruptedException e) {
@ -219,11 +233,12 @@ public class ToolTaskManager implements Runnable {
* @param cmd background command to be scheduled
* @param obj domain object that supports undo/redo
*/
public synchronized void scheduleFollowOnCommand(BackgroundCommand cmd,
UndoableDomainObject obj) {
public synchronized <T extends DomainObject> void scheduleFollowOnCommand(
BackgroundCommand<T> cmd, T obj) {
if (isProcessingDomainObject(obj)) {
PriorityQueue<BackgroundCommand> queue = queuedCommandsMap.get(obj);
PriorityQueue<BackgroundCommand<?>> queue = queuedCommandsMap.get(obj);
if (queue == null) {
queue = new PriorityQueue<>();
queuedCommandsMap.put(obj, queue);
@ -239,7 +254,7 @@ public class ToolTaskManager implements Runnable {
}
}
private boolean isProcessingDomainObject(UndoableDomainObject obj) {
private boolean isProcessingDomainObject(DomainObject obj) {
if (taskThread == null) {
return false;
}
@ -252,9 +267,10 @@ public class ToolTaskManager implements Runnable {
return currentTask != null && !currentTask.isDoneQueueProcessing();
}
private boolean mergeMergeableBackgroundCommands(BackgroundCommand newCommand,
PriorityQueue<BackgroundCommand> queue) {
BackgroundCommand lastCommand = queue.getLast();
private <T extends DomainObject> boolean mergeMergeableBackgroundCommands(
BackgroundCommand<T> newCommand, PriorityQueue<BackgroundCommand<?>> queue) {
@SuppressWarnings("unchecked")
BackgroundCommand<T> lastCommand = (BackgroundCommand<T>) queue.getLast();
if (!(lastCommand instanceof MergeableBackgroundCommand) ||
!(newCommand instanceof MergeableBackgroundCommand)) {
return false;
@ -262,10 +278,10 @@ public class ToolTaskManager implements Runnable {
// merge the two into the original command, as it is still in the queue in the correct
// place
MergeableBackgroundCommand mergeableBackgroundCommand =
(MergeableBackgroundCommand) lastCommand;
MergeableBackgroundCommand newMergeableBackgroundCommand =
(MergeableBackgroundCommand) newCommand;
MergeableBackgroundCommand<T> mergeableBackgroundCommand =
(MergeableBackgroundCommand<T>) lastCommand;
MergeableBackgroundCommand<T> newMergeableBackgroundCommand =
(MergeableBackgroundCommand<T>) newCommand;
mergeableBackgroundCommand.mergeCommands(newMergeableBackgroundCommand);
return true;
}
@ -315,9 +331,10 @@ public class ToolTaskManager implements Runnable {
Msg.debug(this, time() + "Background processing started...");
startQueueTime = System.currentTimeMillis();
for (BackgroundCommandTask task = getNextTask(); task != null; task = getNextTask()) {
for (BackgroundCommandTask<?> task = getNextTask(); task != null; task =
getNextTask()) {
Msg.debug(this, time() + "Exec Task " + task.getTaskTitle());
Msg.debug(this, time() + "Task Start: " + task.getTaskTitle());
startTaskTime = System.currentTimeMillis();
synchronized (this) {
@ -345,7 +362,7 @@ public class ToolTaskManager implements Runnable {
}
}
private synchronized BackgroundCommandTask getNextTask() {
private synchronized BackgroundCommandTask<?> getNextTask() {
if (tasks.isEmpty()) {
taskThread = null;
return null;
@ -353,12 +370,13 @@ public class ToolTaskManager implements Runnable {
return tasks.removeFirst();
}
private synchronized BackgroundCommand getNextCommand(UndoableDomainObject obj) {
PriorityQueue<BackgroundCommand> queue = queuedCommandsMap.get(obj);
private synchronized <T extends DomainObject> BackgroundCommand<T> getNextCommand(T obj) {
PriorityQueue<BackgroundCommand<?>> queue = queuedCommandsMap.get(obj);
if (queue == null) {
return null;
}
BackgroundCommand cmd = queue.removeFirst();
@SuppressWarnings("unchecked")
BackgroundCommand<T> cmd = (BackgroundCommand<T>) queue.removeFirst();
if (queue.isEmpty()) {
queuedCommandsMap.remove(obj);
}
@ -373,15 +391,14 @@ public class ToolTaskManager implements Runnable {
* @param task background command task that has completed
* @param monitor task monitor
*/
public void taskCompleted(UndoableDomainObject obj, BackgroundCommandTask task,
public <T extends DomainObject> void taskCompleted(T obj, BackgroundCommandTask<T> task,
TaskMonitor monitor) {
double taskTime = (System.currentTimeMillis() - startTaskTime) / 1000.00;
Msg.debug(this, time() + task.getTaskTitle() + " task finish (" + taskTime + " secs)");
obj.flushEvents();
try {
while (!monitor.isCancelled()) {
BackgroundCommand cmd;
BackgroundCommand<T> cmd;
synchronized (this) {
cmd = getNextCommand(obj);
if (cmd == null) {
@ -390,13 +407,14 @@ public class ToolTaskManager implements Runnable {
break;
}
}
Msg.debug(this, time() + "Queue - " + cmd.getName());
Msg.debug(this, time() + "Start: " + cmd.getName());
toolTaskMonitor.updateTaskCmd(cmd);
long localStart = System.currentTimeMillis();
cmd.applyTo(obj, monitor);
cmd.taskCompleted();
double totalTime = (System.currentTimeMillis() - localStart) / 1000.00;
Msg.debug(this, time() + "(" + totalTime + " secs)");
Msg.debug(this,
time() + "Completed: " + cmd.getName() + " (" + totalTime + " secs)");
obj.flushEvents();
}
}
@ -408,12 +426,6 @@ public class ToolTaskManager implements Runnable {
tool.setStatusInfo(task.getCommand().getName() + " cancelled");
}
}
synchronized (this) {
Integer openForgroundTransactionID = openForgroundTransactionIDs.remove(obj);
if (openForgroundTransactionID != null) {
obj.endTransaction(openForgroundTransactionID, true);
}
}
}
finally {
if (currentTask.isModal()) {
@ -427,20 +439,21 @@ public class ToolTaskManager implements Runnable {
}
task.getCommand().taskCompleted();
double totalTime = (System.currentTimeMillis() - startTaskTime) / 1000.00;
Msg.debug(this, time() + task.getTaskTitle() + " task complete (" + totalTime + " secs)");
Msg.debug(this,
time() + "Task Completed: " + task.getTaskTitle() + " (" + totalTime + " secs)");
}
/**
* Clear the queue of scheduled commands.
* @param obj domain object
*/
public synchronized void clearQueuedCommands(UndoableDomainObject obj) {
PriorityQueue<BackgroundCommand> queue = queuedCommandsMap.get(obj);
public synchronized void clearQueuedCommands(DomainObject obj) {
PriorityQueue<BackgroundCommand<?>> queue = queuedCommandsMap.get(obj);
if (queue == null) {
return;
}
while (!queue.isEmpty()) {
BackgroundCommand cmd = queue.removeFirst();
BackgroundCommand<?> cmd = queue.removeFirst();
cmd.dispose();
}
queuedCommandsMap.remove(obj);
@ -451,10 +464,10 @@ public class ToolTaskManager implements Runnable {
*
* @param obj domain object
*/
public synchronized void clearTasks(UndoableDomainObject obj) {
Iterator<BackgroundCommandTask> iter = tasks.iterator();
public synchronized void clearTasks(DomainObject obj) {
Iterator<BackgroundCommandTask<?>> iter = tasks.iterator();
while (iter.hasNext()) {
BackgroundCommandTask task = iter.next();
BackgroundCommandTask<?> task = iter.next();
if (task.getDomainObject() == obj) {
iter.remove();
}
@ -469,7 +482,7 @@ public class ToolTaskManager implements Runnable {
* @param taskCmd background command that failed
* @param monitor task monitor for the background task
*/
public void taskFailed(UndoableDomainObject obj, BackgroundCommand taskCmd,
public <T extends DomainObject> void taskFailed(T obj, BackgroundCommand<T> taskCmd,
TaskMonitor monitor) {
try {
obj.flushEvents();
@ -499,36 +512,15 @@ public class ToolTaskManager implements Runnable {
}
}
private void executeQueueCommands(UndoableDomainObject obj, String title) {
obj.flushEvents();
synchronized (this) {
PriorityQueue<BackgroundCommand> queue = queuedCommandsMap.get(obj);
if (queue == null) {
return; // nothing is queued
}
if (!openForgroundTransactionIDs.containsKey(obj)) {
// persist transaction to include follow-on changes
openForgroundTransactionIDs.put(obj, obj.startTransaction(title));
}
}
// schedule task to process command queue
BackgroundCommand cmd = new EmptyBackgroundCommand();
executeCommand(cmd, obj);
}
/**
* Clear list of tasks and queue of scheduled commands.
*/
public synchronized void dispose() {
clearTasks();
List<UndoableDomainObject> list = new ArrayList<>(queuedCommandsMap.keySet());
for (UndoableDomainObject obj : list) {
List<DomainObject> list = new ArrayList<>(queuedCommandsMap.keySet());
for (DomainObject obj : list) {
clearQueuedCommands(obj);
Integer txId = openForgroundTransactionIDs.get(obj);
if (txId != null) {
obj.endTransaction(txId, true);
}
}
queuedCommandsMap = new HashMap<>();
@ -567,9 +559,7 @@ public class ToolTaskManager implements Runnable {
}
private synchronized boolean hasQueuedTasksForDomainObject(DomainObject domainObject) {
Iterator<BackgroundCommandTask> iter = tasks.iterator();
while (iter.hasNext()) {
BackgroundCommandTask task = iter.next();
for (BackgroundCommandTask<?> task : tasks) {
if (task.getDomainObject() == domainObject) {
return true;
}
@ -577,21 +567,49 @@ public class ToolTaskManager implements Runnable {
return false;
}
}
private static class EmptyBackgroundCommand<T extends DomainObject>
extends BackgroundCommand<T> {
class EmptyBackgroundCommand extends BackgroundCommand {
public EmptyBackgroundCommand(String name) {
super(name, false, true, false);
}
public EmptyBackgroundCommand() {
super("Empty Background Command", false, true, false);
@Override
public boolean applyTo(T obj, TaskMonitor monitor) {
return true;
}
}
/**
* @see ghidra.framework.cmd.BackgroundCommand#applyTo(ghidra.framework.model.DomainObject,
* ghidra.util.task.TaskMonitor)
* {@link SimpleCommand} provides a convenience command for wrapping a lambda function
* into a foreground {@link Command} for execution by the task manager.
*
* @param <T> {@link DomainObject} implementation class
*/
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
return true;
private static class SimpleCommand<T extends DomainObject> implements Command<T> {
private String commandName;
private Function<T, Boolean> f;
SimpleCommand(String commandName, Function<T, Boolean> f) {
this.commandName = commandName;
this.f = f;
}
@Override
public boolean applyTo(T domainObject) {
return f.apply(domainObject);
}
@Override
public String getStatusMsg() {
return null;
}
@Override
public String getName() {
return commandName;
}
}
}
@ -614,7 +632,7 @@ class ToolTaskMonitor extends TaskMonitorComponent implements TaskListener {
};
}
public void updateTaskCmd(BackgroundCommand cmd) {
public void updateTaskCmd(BackgroundCommand<?> cmd) {
showProgress(cmd.hasProgress());
setTaskName(cmd.getName());
}

View file

@ -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.
@ -16,7 +15,7 @@
*/
package ghidra.framework.task;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -39,7 +38,6 @@ public interface GTask {
* @param monitor the taskMonitor to be used to cancel and report progress.
* @throws CancelledException if the user cancelled the task.
*/
public void run(UndoableDomainObject domainObject, TaskMonitor monitor)
throws CancelledException;
public void run(DomainObject domainObject, TaskMonitor monitor) throws CancelledException;
}

View file

@ -21,13 +21,14 @@ import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectClosedListener;
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
* tasks pertain to an DomainObject and transactions are created on the DomainObject
* so that tasks can operate on them.
* <P>
* Tasks are organized into groups such that all tasks in a group will be completed before the
@ -36,7 +37,7 @@ import ghidra.util.exception.CancelledException;
* in the order that they are scheduled.
* <P>
* All tasks within the same group are executed within the same transaction on the
* UndoableDomainObject. When all the tasks within a group are completed, the transaction is closed
* DomainObject. When all the tasks within a group are completed, the transaction is closed
* unless there is another group scheduled and that group does not specify that it should run in its
* own transaction.
* <P>
@ -59,7 +60,7 @@ public class GTaskManager {
private static final int MAX_RESULTS = 100;
private UndoableDomainObject domainObject;
private DomainObject domainObject; // value will be set to null on close
private SortedSet<GScheduledTask> priorityQ = new TreeSet<GScheduledTask>();
private Deque<GTaskGroup> taskGroupList = new LinkedList<GTaskGroup>();
private GThreadPool threadPool;
@ -82,14 +83,14 @@ public class GTaskManager {
private Queue<GTaskResult> results = new ArrayDeque<GTaskResult>();
/**
* Creates a new GTaskManager for an UndoableDomainObject
* @param undoableDomainObject the domainObject that tasks scheduled in this GTaskManager will
* Creates a new GTaskManager for an DomainObject
* @param domainObject the domainObject that tasks scheduled in this GTaskManager will
* operate upon.
* @param threadPool the GThreadPool that will provide the threads that will be used to run
* tasks in this GTaskManager.
*/
public GTaskManager(UndoableDomainObject undoableDomainObject, GThreadPool threadPool) {
this.domainObject = undoableDomainObject;
public GTaskManager(DomainObject domainObject, GThreadPool threadPool) {
this.domainObject = domainObject;
this.threadPool = threadPool;
domainObject.addCloseListener(new DomainObjectClosedListener() {
@ -97,7 +98,7 @@ public class GTaskManager {
public void domainObjectClosed(DomainObject dobj) {
// assert dobj == domainObj
GTaskManagerFactory.domainObjectClosed(domainObject);
domainObject = null;
GTaskManager.this.domainObject = null;
}
});
}
@ -111,6 +112,7 @@ public class GTaskManager {
* if one exists. If false, any open transaction
* will be closed and a new transaction will be opened before
* this task is run.
* @return scheduled task
*/
public GScheduledTask scheduleTask(GTask task, int priority, boolean useCurrentGroup) {
GScheduledTask newTask;
@ -154,7 +156,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 groupName. The name of the group that the task will be added to.
* @param groupName The name of the group that the task will be added to.
*/
public void scheduleTask(GTask task, int priority, String groupName) {
lock.lock();
@ -616,14 +618,16 @@ public class GTaskManager {
}
private void openTransaction(String description) {
if (currentGroupTransactionID == null) {
DomainObject d = domainObject;
if (d != null && currentGroupTransactionID == null) {
currentGroupTransactionID = domainObject.startTransaction(description);
}
}
private void closeTransaction() {
if (currentGroupTransactionID != null) {
domainObject.endTransaction(currentGroupTransactionID, true);
DomainObject d = domainObject;
if (d != null && currentGroupTransactionID != null) {
d.endTransaction(currentGroupTransactionID, true);
currentGroupTransactionID = null;
}
}
@ -763,12 +767,13 @@ public class GTaskManager {
scheduledTask.setThread(Thread.currentThread());
notifyTaskStarted(scheduledTask);
if (scheduledTask.getGroup().wasCancelled()) {
DomainObject d = domainObject;
if (d == null || scheduledTask.getGroup().wasCancelled()) {
taskCompleted(scheduledTask, new CancelledException());
return;
}
scheduledTask.getTask().run(domainObject, scheduledTask.getTaskMonitor());
scheduledTask.getTask().run(d, scheduledTask.getTaskMonitor());
taskCompleted(scheduledTask, null);
}
catch (Exception e) {

View file

@ -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.
@ -16,20 +15,20 @@
*/
package ghidra.framework.task;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.exception.AssertException;
import java.util.Map;
import java.util.WeakHashMap;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.AssertException;
/**
* Factory class managing a single GTaskManager for an UndoableDomainObject.
* Factory class managing a single GTaskManager for an DomainObject.
*
*/
public class GTaskManagerFactory {
private static Map<UndoableDomainObject, GTaskManager> map =
new WeakHashMap<UndoableDomainObject, GTaskManager>();
private static Map<DomainObject, GTaskManager> map =
new WeakHashMap<DomainObject, GTaskManager>();
/**
* Returns the one GTaskManager for the domainObject. A new GTaskManager will be created if
@ -38,7 +37,7 @@ public class GTaskManagerFactory {
* @param domainObject the domainObject for which to get a GTaskManager.
* @return the GTaskManager for the given domainObject.
*/
public static GTaskManager getTaskManager(UndoableDomainObject domainObject) {
public static GTaskManager getTaskManager(DomainObject domainObject) {
if (domainObject.isClosed()) {
throw new AssertException("Attempted to get a TaskManger for a closed domain object");
}
@ -51,7 +50,7 @@ public class GTaskManagerFactory {
return gTaskManager;
}
static void domainObjectClosed(UndoableDomainObject domainObject) {
static void domainObjectClosed(DomainObject domainObject) {
map.remove(domainObject);
}
}

View file

@ -28,7 +28,7 @@ import org.junit.*;
import docking.test.AbstractDockingTest;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.model.DomainObject;
import ghidra.framework.task.*;
import ghidra.framework.task.gui.taskview.*;
import ghidra.util.exception.CancelledException;
@ -412,8 +412,9 @@ public class GTaskGUITest extends AbstractDockingTest {
private void assertWaitingCount(int count) {
waitForSwing();
waitForCondition(() -> count == getWaitingCount(), "Timed-out waiting for the 'wait count' to be " + count + ", but was " +
getWaitingCount());
waitForCondition(() -> count == getWaitingCount(),
"Timed-out waiting for the 'wait count' to be " + count + ", but was " +
getWaitingCount());
}
private int getWaitingCount() {
@ -647,11 +648,12 @@ public class GTaskGUITest extends AbstractDockingTest {
}
void waitForWorkFinished(int n) {
waitForCondition(() -> workCount.get() == n, "Work iteration " + n + " never completed");
waitForCondition(() -> workCount.get() == n,
"Work iteration " + n + " never completed");
}
@Override
public void run(UndoableDomainObject obj, TaskMonitor monitor) throws CancelledException {
public void run(DomainObject obj, TaskMonitor monitor) throws CancelledException {
debug(getName() + ": Run called");
monitor.initialize(loopCount);
taskStartLatch.countDown();

View file

@ -28,7 +28,7 @@ import org.junit.*;
import generic.concurrent.GThreadPool;
import generic.test.AbstractGenericTest;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -480,7 +480,7 @@ public class GTaskTest extends AbstractGenericTest {
}
@Override
public void run(UndoableDomainObject obj, TaskMonitor monitor) throws CancelledException {
public void run(DomainObject obj, TaskMonitor monitor) throws CancelledException {
try {
if (!latch.await(2, TimeUnit.SECONDS)) {
Assert.fail("Latch await expired!");
@ -500,7 +500,7 @@ public class GTaskTest extends AbstractGenericTest {
}
@Override
public void run(UndoableDomainObject obj, TaskMonitor monitor) throws CancelledException {
public void run(DomainObject obj, TaskMonitor monitor) throws CancelledException {
GTaskManager taskManager = GTaskManagerFactory.getTaskManager(obj);
taskManager.waitForHigherPriorityTasks();
super.run(obj, monitor);

View file

@ -15,7 +15,7 @@
*/
package ghidra.framework.task;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -35,7 +35,7 @@ public class SimpleTask implements GTask {
}
@Override
public void run(UndoableDomainObject obj, TaskMonitor monitor) throws CancelledException {
public void run(DomainObject obj, TaskMonitor monitor) throws CancelledException {
monitor.checkCancelled();
didRun = true;
}

View file

@ -28,7 +28,7 @@ import docking.widgets.checkbox.GCheckBox;
import generic.concurrent.GThreadPool;
import ghidra.GhidraApplicationLayout;
import ghidra.framework.Application;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -193,8 +193,7 @@ public class TaskSimulator {
}
@Override
public void run(UndoableDomainObject domainObject, TaskMonitor monitor)
throws CancelledException {
public void run(DomainObject domainObject, TaskMonitor monitor) throws CancelledException {
monitor.initialize(count);