mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-4390 Collapse DomainObject Undoable interfaces and refactor Command processing.
This commit is contained in:
parent
136b933af2
commit
445494ba25
214 changed files with 2862 additions and 3813 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue