diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapModel.java index 5a5576c619..24481bbc6c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapModel.java @@ -272,48 +272,36 @@ class MemoryMapModel extends AbstractSortedTableModel { break; } if (!name.equals(block.getName())) { - int id = program.startTransaction("Rename Memory Block"); - try { - block.setName(name); - program.endTransaction(id, true); - } - catch (LockException e) { - program.endTransaction(id, false); - this.provider.setStatusText(e.getMessage()); - return; - } - catch (RuntimeException e1) { - program.endTransaction(id, false); - throw e1; - } + + program.withTransaction("Rename Memory Block", () -> { + try { + block.setName(name); + } + catch (LockException e) { + provider.setStatusText(e.getMessage()); + } + }); + } break; case READ: { - int id = program.startTransaction("Set Read State"); - try { + + program.withTransaction("Set Read State", () -> { boolean value = ((Boolean) aValue).booleanValue(); block.setRead(value); provider.setStatusText(""); - program.endTransaction(id, true); - } - catch (RuntimeException e) { - program.endTransaction(id, false); - throw e; - } + }); + break; } case WRITE: { - int id = program.startTransaction("Set Write State"); - try { + + program.withTransaction("Set Write State", () -> { boolean value = ((Boolean) aValue).booleanValue(); block.setWrite(value); provider.setStatusText(""); - program.endTransaction(id, true); - } - catch (RuntimeException e) { - program.endTransaction(id, false); - throw e; - } + }); + break; } case EXECUTE: { @@ -514,8 +502,9 @@ class MemoryMapModel extends AbstractSortedTableModel { } private String getByteSourceDescription(List sourceInfos) { - List limited = sourceInfos.size() < 5 ? sourceInfos : sourceInfos.subList(0, 4); - + List limited = + sourceInfos.size() < 5 ? sourceInfos : sourceInfos.subList(0, 4); + //@formatter:off String description = limited .stream() diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/UndoableDomainObject.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/UndoableDomainObject.java index c9fae71db0..9a63b6f98f 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/UndoableDomainObject.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/UndoableDomainObject.java @@ -18,6 +18,8 @@ package ghidra.framework.model; import db.TerminatedTransactionException; import db.Transaction; import ghidra.framework.store.LockException; +import utility.function.ExceptionalCallback; +import utility.function.ExceptionalSupplier; /** * UndoableDomainObject extends a domain object to provide transaction @@ -48,6 +50,72 @@ public interface UndoableDomainObject extends DomainObject, Undoable { */ 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. + *

+ *

+	 * program.withTransaction("My Description", () -> {
+	 * 	// ... Do something
+	 * });
+	 * 
+ * + *

+ * 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 void withTransaction(String description, + ExceptionalCallback 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. + *

+ *

+	 * program.withTransaction("My Description", () -> {
+	 * 	// ... Do something
+	 * 	return result;
+	 * });
+	 * 
+ *

+ * If you do not need to supply a result, then use + * {@link #withTransaction(String, ExceptionalCallback)} instead. + * + * @param the exception that may be thrown from this method + * @param 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 T withTransaction(String description, + ExceptionalSupplier 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. @@ -90,8 +158,8 @@ public interface UndoableDomainObject extends DomainObject, Undoable { public TransactionInfo getCurrentTransactionInfo(); /** - * Returns true if the last transaction was terminated externally from the action that - * started it. + * 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(); @@ -108,7 +176,7 @@ public interface UndoableDomainObject extends DomainObject, Undoable { * 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 + * @param domainObj the domain object * @throws LockException if lock or open transaction is active on either * this or the specified domain object */