GP-3122 Added AutoCloseable Transaction API to DBHandle and

UndoableDomainObject.  Performed renaming of some internal classes.
This commit is contained in:
ghidra1 2023-02-28 14:16:25 -05:00
parent b4de95f4f5
commit 1795c35dfc
209 changed files with 1892 additions and 2054 deletions

View file

@ -53,7 +53,7 @@ abstract class AbstractTransactionManager {
checkLockingTask();
synchronized (this) {
if (getCurrentTransaction() != null && !transactionTerminated) {
if (getCurrentTransactionInfo() != null && !transactionTerminated) {
return false;
}
if (lockCount == 0) {
@ -85,7 +85,7 @@ abstract class AbstractTransactionManager {
return null;
}
checkDomainObject(domainObj);
if (lockCount != 0 || getCurrentTransaction() != null || lockingTaskMonitor != null) {
if (lockCount != 0 || getCurrentTransactionInfo() != null || lockingTaskMonitor != null) {
return null;
}
++lockCount; // prevent prepareToSave
@ -192,14 +192,15 @@ abstract class AbstractTransactionManager {
}
final int startTransaction(DomainObjectAdapterDB object, String description,
AbortedTransactionListener listener, boolean notify) {
AbortedTransactionListener listener, boolean notify)
throws TerminatedTransactionException {
checkLockingTask();
synchronized (this) {
checkDomainObject(object);
if (getCurrentTransaction() != null && transactionTerminated) {
if (getCurrentTransactionInfo() != null && transactionTerminated) {
throw new TerminatedTransactionException();
}
@ -210,8 +211,8 @@ abstract class AbstractTransactionManager {
abstract int startTransaction(DomainObjectAdapterDB object, String description,
AbortedTransactionListener listener, boolean force, boolean notify);
abstract Transaction endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify);
abstract TransactionInfo endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify) throws IllegalStateException;
/**
* Returns the undo stack depth.
@ -229,14 +230,14 @@ abstract class AbstractTransactionManager {
abstract String getUndoName();
abstract Transaction getCurrentTransaction();
abstract TransactionInfo getCurrentTransactionInfo();
final void redo() throws IOException {
checkLockingTask();
synchronized (this) {
if (getCurrentTransaction() != null) {
if (getCurrentTransactionInfo() != null) {
throw new IllegalStateException("Can not redo while transaction is open");
}
verifyNoLock();
@ -251,9 +252,9 @@ abstract class AbstractTransactionManager {
checkLockingTask();
synchronized (this) {
if (getCurrentTransaction() != null) {
if (getCurrentTransactionInfo() != null) {
throw new IllegalStateException("Can not undo while transaction is open: " +
getCurrentTransaction().getDescription());
getCurrentTransactionInfo().getDescription());
}
verifyNoLock();
doUndo(true);

View file

@ -20,8 +20,7 @@ import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import db.DBConstants;
import db.DBHandle;
import db.*;
import db.util.ErrorHandler;
import ghidra.framework.model.*;
import ghidra.framework.options.Options;
@ -32,7 +31,6 @@ import ghidra.util.Msg;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
/**
* Database version of the DomainObjectAdapter; this version adds the
@ -273,7 +271,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
@Override
public boolean canLock() {
return transactionMgr.getCurrentTransaction() == null && !closed;
return transactionMgr.getCurrentTransactionInfo() == null && !closed;
}
@Override
@ -329,18 +327,40 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
}
@Override
public int startTransaction(String description) {
public Transaction openTransaction(String description)
throws TerminatedTransactionException, IllegalStateException {
return new Transaction() {
int txId = startTransaction(description);
@Override
protected boolean endTransaction(boolean commit) {
DomainObjectAdapterDB.this.endTransaction(txId, commit);
return commit;
}
@Override
public boolean isSubTransaction() {
return true;
}
};
}
@Override
public int startTransaction(String description) throws TerminatedTransactionException {
return startTransaction(description, null);
}
@Override
public int startTransaction(String description, AbortedTransactionListener listener) {
public int startTransaction(String description, AbortedTransactionListener listener)
throws TerminatedTransactionException {
return startTransaction(description, listener, tryForeverExceptionHandler);
}
// open for testing
int startTransaction(String description, AbortedTransactionListener listener,
Function<DomainObjectLockedException, Boolean> exceptionHandler) {
Function<DomainObjectLockedException, Boolean> exceptionHandler)
throws TerminatedTransactionException {
while (true) {
try {
@ -355,7 +375,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
}
@Override
public void endTransaction(int transactionID, boolean commit) {
public void endTransaction(int transactionID, boolean commit) throws IllegalStateException {
transactionMgr.endTransaction(this, transactionID, commit, true);
}
@ -408,8 +428,8 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
}
@Override
public Transaction getCurrentTransaction() {
return transactionMgr.getCurrentTransaction();
public TransactionInfo getCurrentTransactionInfo() {
return transactionMgr.getCurrentTransactionInfo();
}
@Override

View file

@ -28,14 +28,14 @@ import ghidra.util.datastruct.WeakSet;
* <code>DomainObjectDBTransaction</code> represents an atomic undoable operation performed
* on a single domain object.
*/
class DomainObjectDBTransaction implements Transaction {
class DomainObjectDBTransaction implements TransactionInfo {
private static int nextBaseId = 1234;
private ArrayList<TransactionEntry> list;
private HashMap<PluginTool, ToolState> toolStates;
private int activeEntries = 0;
private int status = NOT_DONE;
private Status status = Status.NOT_DONE;
private boolean hasDBTransaction = false;
private final long id;
private WeakSet<AbortedTransactionListener> abortedTransactionListeners =
@ -106,7 +106,7 @@ class DomainObjectDBTransaction implements Transaction {
* database transaction/checkpoint.
*/
void setHasCommittedDBTransaction() {
if (getStatus() != COMMITTED) {
if (getStatus() != Status.COMMITTED) {
throw new IllegalStateException("transaction was not committed");
}
hasDBTransaction = true;
@ -147,23 +147,23 @@ class DomainObjectDBTransaction implements Transaction {
catch (IndexOutOfBoundsException e) {
throw new IllegalStateException("Transaction not found");
}
if (entry.status != NOT_DONE) {
if (entry.status != Status.NOT_DONE) {
throw new IllegalStateException("Attempted to end Transaction " + "more that once: " +
entry.description);
}
entry.status = commit ? COMMITTED : ABORTED;
entry.status = commit ? Status.COMMITTED : Status.ABORTED;
if (!commit) {
status = ABORTED;
status = Status.ABORTED;
}
if (--activeEntries == 0 && status == NOT_DONE) {
status = COMMITTED;
if (--activeEntries == 0 && status == Status.NOT_DONE) {
status = Status.COMMITTED;
}
}
@Override
public int getStatus() {
if (status == ABORTED && activeEntries > 0) {
return NOT_DONE_BUT_ABORTED;
public Status getStatus() {
if (status == Status.ABORTED && activeEntries > 0) {
return Status.NOT_DONE_BUT_ABORTED;
}
return status;
}
@ -224,7 +224,7 @@ class DomainObjectDBTransaction implements Transaction {
Iterator<TransactionEntry> iter = list.iterator();
while (iter.hasNext()) {
TransactionEntry entry = iter.next();
if (entry.status == NOT_DONE) {
if (entry.status == Status.NOT_DONE) {
subTxList.add(entry.description);
}
}
@ -233,11 +233,11 @@ class DomainObjectDBTransaction implements Transaction {
private static class TransactionEntry {
String description;
int status;
Status status;
TransactionEntry(String description) {
this.description = description;
status = NOT_DONE;
status = Status.NOT_DONE;
}
}
@ -256,7 +256,7 @@ class DomainObjectDBTransaction implements Transaction {
}
void abort() {
status = ABORTED;
status = Status.ABORTED;
for (AbortedTransactionListener listener : abortedTransactionListeners) {
listener.transactionAborted(id);
}

View file

@ -19,6 +19,7 @@ import java.io.IOException;
import java.util.LinkedList;
import ghidra.framework.model.*;
import ghidra.framework.model.TransactionInfo.Status;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
@ -138,8 +139,8 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
}
@Override
synchronized Transaction endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify) {
synchronized TransactionInfo endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify) throws IllegalStateException {
if (object != domainObj) {
throw new IllegalArgumentException("invalid domain object");
}
@ -149,8 +150,8 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
DomainObjectDBTransaction returnedTransaction = transaction;
try {
transaction.endEntry(transactionID, commit && !transactionTerminated);
int status = transaction.getStatus();
if (status == Transaction.COMMITTED) {
Status status = transaction.getStatus();
if (status == Status.COMMITTED) {
object.flushWriteCache();
boolean committed = domainObj.dbh.endTransaction(transaction.getID(), true);
if (committed) {
@ -171,7 +172,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
}
transaction = null;
}
else if (status == Transaction.ABORTED) {
else if (status == Status.ABORTED) {
object.invalidateWriteCache();
if (!transactionTerminated) {
domainObj.dbh.endTransaction(transaction.getID(), false);
@ -225,7 +226,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
@Override
synchronized String getRedoName() {
if (transaction == null && redoList.size() > 0) {
Transaction t = redoList.getLast();
TransactionInfo t = redoList.getLast();
return t.getDescription();
}
return "";
@ -234,14 +235,14 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
@Override
synchronized String getUndoName() {
if (transaction == null && undoList.size() > 0) {
Transaction t = undoList.getLast();
TransactionInfo t = undoList.getLast();
return t.getDescription();
}
return "";
}
@Override
Transaction getCurrentTransaction() {
TransactionInfo getCurrentTransactionInfo() {
return transaction;
}
@ -318,7 +319,7 @@ class DomainObjectTransactionManager extends AbstractTransactionManager {
transactionListeners.remove(listener);
}
void notifyStartTransaction(Transaction tx) {
void notifyStartTransaction(TransactionInfo tx) {
SystemUtilities.runSwingLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.transactionStarted(domainObj, tx);

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,17 +15,17 @@
*/
package ghidra.framework.data;
import ghidra.framework.model.AbortedTransactionListener;
import ghidra.framework.model.Transaction;
import java.io.IOException;
import java.util.ArrayList;
import ghidra.framework.model.AbortedTransactionListener;
import ghidra.framework.model.TransactionInfo;
/**
* <code>SynchronizedTransaction</code> represents an atomic undoable operation performed
* on a synchronized set of domain objects.
*/
class SynchronizedTransaction implements Transaction {
class SynchronizedTransaction implements TransactionInfo {
private DomainObjectTransactionManager[] managers;
private int[] holdTransactionIds;
@ -34,7 +33,7 @@ class SynchronizedTransaction implements Transaction {
private String[] descriptions;
private int[] activeCounts;
private int status = NOT_DONE;
private Status status = Status.NOT_DONE;
private final long id;
SynchronizedTransaction(DomainObjectTransactionManager[] managers) {
@ -81,13 +80,13 @@ class SynchronizedTransaction implements Transaction {
@Override
public ArrayList<String> getOpenSubTransactions() {
ArrayList<String> list = new ArrayList<String>();
int status = getStatus();
if (status == ABORTED || status == COMMITTED) {
Status status = getStatus();
if (status == Status.ABORTED || status == Status.COMMITTED) {
return list;
}
for (int i = 0; i < managers.length; i++) {
String name = getDomainObjectName(managers[i]);
for (String str : managers[i].getCurrentTransaction().getOpenSubTransactions()) {
for (String str : managers[i].getCurrentTransactionInfo().getOpenSubTransactions()) {
list.add(name + ": " + str);
}
}
@ -104,9 +103,9 @@ class SynchronizedTransaction implements Transaction {
}
@Override
public int getStatus() {
if (status == ABORTED && isActive()) {
return NOT_DONE_BUT_ABORTED;
public Status getStatus() {
if (status == Status.ABORTED && isActive()) {
return Status.NOT_DONE_BUT_ABORTED;
}
return status;
}
@ -126,11 +125,11 @@ class SynchronizedTransaction implements Transaction {
int index = findDomainObject(domainObj);
managers[index].endTransaction(domainObj, transactionID, commit, false);
if (!commit) {
status = ABORTED;
status = Status.ABORTED;
}
--activeCounts[index];
if (!isActive() && status == NOT_DONE) {
status = COMMITTED;
if (!isActive() && status == Status.NOT_DONE) {
status = Status.COMMITTED;
}
}
@ -153,7 +152,7 @@ class SynchronizedTransaction implements Transaction {
boolean endAll(boolean commit) {
boolean hasChange = false;
for (int i = 0; i < managers.length; i++) {
Transaction transaction = managers[i].endTransaction(managers[i].getDomainObject(),
TransactionInfo transaction = managers[i].endTransaction(managers[i].getDomainObject(),
holdTransactionIds[i], commit, false);
if (commit && transaction.hasCommittedDBTransaction()) {
hasChanges[i] = true;

View file

@ -19,6 +19,7 @@ import java.io.IOException;
import java.util.LinkedList;
import ghidra.framework.model.*;
import ghidra.framework.model.TransactionInfo.Status;
import ghidra.framework.store.LockException;
import ghidra.util.Msg;
@ -59,8 +60,8 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
if (!(mgr instanceof DomainObjectTransactionManager)) {
throw new IllegalArgumentException("domain object has invalid transaction manager");
}
if (isLocked() || mgr.isLocked() || getCurrentTransaction() != null ||
mgr.getCurrentTransaction() != null) {
if (isLocked() || mgr.isLocked() || getCurrentTransactionInfo() != null ||
mgr.getCurrentTransactionInfo() != null) {
throw new LockException("domain object(s) are busy/locked");
}
if (!mgr.lock("Transaction manager join")) {
@ -86,9 +87,9 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
}
synchronized void removeDomainObject(DomainObjectAdapterDB domainObj) throws LockException {
if (getCurrentTransaction() != null) {
if (getCurrentTransactionInfo() != null) {
throw new LockException(
"domain object has open transaction: " + getCurrentTransaction().getDescription());
"domain object has open transaction: " + getCurrentTransactionInfo().getDescription());
}
if (isLocked()) {
throw new LockException("domain object is locked!");
@ -177,15 +178,15 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
}
@Override
synchronized Transaction endTransaction(DomainObjectAdapterDB object, int transactionID,
synchronized TransactionInfo endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify) {
if (transaction == null) {
throw new IllegalStateException("No transaction is open");
}
Transaction returnedTransaction = transaction;
TransactionInfo returnedTransaction = transaction;
transaction.endEntry(object, transactionID, commit && !transactionTerminated);
int status = transaction.getStatus();
if (status == Transaction.COMMITTED) {
Status status = transaction.getStatus();
if (status == Status.COMMITTED) {
boolean committed = transaction.endAll(true);
if (committed) {
redoList.clear();
@ -199,7 +200,7 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
notifyEndTransaction();
}
}
else if (status == Transaction.ABORTED) {
else if (status == Status.ABORTED) {
if (!transactionTerminated) {
transaction.endAll(false);
}
@ -249,7 +250,7 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
@Override
synchronized String getRedoName() {
if (redoList.size() > 0) {
Transaction t = redoList.getLast();
TransactionInfo t = redoList.getLast();
return t.getDescription();
}
return "";
@ -258,14 +259,14 @@ class SynchronizedTransactionManager extends AbstractTransactionManager {
@Override
synchronized String getUndoName() {
if (undoList.size() > 0) {
Transaction t = undoList.getLast();
TransactionInfo t = undoList.getLast();
return t.getDescription();
}
return "";
}
@Override
Transaction getCurrentTransaction() {
TransactionInfo getCurrentTransactionInfo() {
return transaction;
}

View file

@ -370,7 +370,7 @@ class FileActionManager {
buf.append("The File " + files.get(lastIndex).getPathname() +
" is currently being modified by the\n");
buf.append("the following actions:\n \n");
Transaction t = udo.getCurrentTransaction();
TransactionInfo t = udo.getCurrentTransactionInfo();
List<String> list = t.getOpenSubTransactions();
Iterator<String> it = list.iterator();
while (it.hasNext()) {

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.
@ -18,12 +17,11 @@ package ghidra.framework.model;
import java.util.ArrayList;
public interface Transaction {
public interface TransactionInfo {
public static final int NOT_DONE = 0;
public static final int COMMITTED = 1;
public static final int ABORTED = 2;
public static final int NOT_DONE_BUT_ABORTED = 3;
public enum Status {
NOT_DONE, COMMITTED, ABORTED, NOT_DONE_BUT_ABORTED;
}
public long getID();
@ -40,11 +38,16 @@ public interface Transaction {
*/
public ArrayList<String> getOpenSubTransactions();
public int getStatus();
/**
* Get the status of the corresponding transaction.
* @return status
*/
public Status getStatus();
/**
* Returns true if this fully committed transaction has a corresponding
* database transaction/checkpoint.
* Determine if the corresponding transaction, and all of its sub-transactions, has been
* comitted to the underlying database.
* @return true if the corresponding transaction has been comitted, else false.
*/
public boolean hasCommittedDBTransaction();

View file

@ -26,7 +26,7 @@ public interface TransactionListener {
* @param domainObj the domain object where the transaction was started
* @param tx the transaction that was started
*/
void transactionStarted(DomainObjectAdapterDB domainObj, Transaction tx);
void transactionStarted(DomainObjectAdapterDB domainObj, TransactionInfo tx);
/**
* Invoked when a transaction is ended.

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,9 +15,9 @@
*/
package ghidra.framework.model;
import ghidra.framework.store.LockException;
import db.TerminatedTransactionException;
import db.Transaction;
import ghidra.framework.store.LockException;
/**
* <code>UndoableDomainObject</code> extends a domain object to provide transaction
@ -35,6 +34,20 @@ import db.TerminatedTransactionException;
*/
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;
/**
* Start a new transaction in order to make changes to this domain object.
* All changes must be made in the context of a transaction.
@ -71,10 +84,10 @@ public interface UndoableDomainObject extends DomainObject, Undoable {
public void endTransaction(int transactionID, boolean commit);
/**
* Returns the current transaction
* @return the current transaction
* Returns the current transaction info
* @return the current transaction info
*/
public Transaction getCurrentTransaction();
public TransactionInfo getCurrentTransactionInfo();
/**
* Returns true if the last transaction was terminated externally from the action that