GP-2437: Change DBAddressFieldCodec to use FixedField10

This commit is contained in:
Dan 2022-08-15 14:02:22 -04:00
parent b289708a00
commit c0c25e3805
114 changed files with 2354 additions and 652 deletions

View file

@ -64,7 +64,6 @@ public class BangAddressToMemory extends GhidraScript {
private AddressSpace defaultSpace;
private DebuggerModelService modelService;
private DebuggerTraceManagerService managerService;
@ -121,7 +120,7 @@ public class BangAddressToMemory extends GhidraScript {
}
private void parse(String result) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Populate memory", true);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Populate memory");
LockHold hold = trace.lockWrite();) {
//Pattern pattern = Pattern.compile("\\s+(*)\\s+(*)\\s+");
//Matcher matcher = pattern.matcher(fullclassname);

View file

@ -191,7 +191,7 @@ public class PopulateTraceLocal extends GhidraScript {
control.waitForEvent();
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Populate Events", true)) {
UndoableTransaction.start(trace, "Populate Events")) {
List<ModelObject> children =
util.getElements(List.of("Debugger", "State", "DebuggerVariables", "curprocess",
@ -284,7 +284,7 @@ public class PopulateTraceLocal extends GhidraScript {
}
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Populate Registers", true)) {
UndoableTransaction.start(trace, "Populate Registers")) {
//for (Long tick : tickManager.getAllTicks()) {
for (Long snap : eventSnaps) {
control.execute("!tt " + Long.toHexString(snap) + ":0");

View file

@ -151,7 +151,7 @@ public class PopulateTraceRemote extends GhidraScript {
manager = tool.getService(DebuggerTraceManagerService.class);
targets = tool.getService(DebuggerModelService.class);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Populate Events", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Populate Events")) {
timeManager = trace.getTimeManager();
timeManager.createSnapshot("init");
}

View file

@ -81,7 +81,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
.getProjectData()
.getRootFolder()
.createFile("emu_example", program, monitor);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
entry = space.getAddress(0x00400000);
Address dataEntry = space.getAddress(0x00600000);
@ -163,7 +163,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
*/
TraceTimeManager time = trace.getTimeManager();
TraceSnapshot snapshot = time.getSnapshot(0, true);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
for (int i = 0; i < 10; i++) {
println("Executing: " + thread.getCounter());
thread.stepInstruction();

View file

@ -295,7 +295,7 @@ public class PopulateDemoTrace extends GhidraScript {
* object.
*/
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Populate First Snapshot", true)) {
UndoableTransaction.start(trace, "Populate First Snapshot")) {
/**
* While not strictly required, each tick should be explicitly added to the database and
* given a description. Some things may mis-behave if there does not exist at least one
@ -439,7 +439,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* Just hand emulate the stepping
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap = trace.getTimeManager().createSnapshot("Stepped: PUSH RBP").getKey();
stack1offset -= 8;
@ -466,7 +466,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* More hand emulation
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap = trace.getTimeManager().createSnapshot("Stepped: MOV RBP,RSP").getKey();
putRIP(snap, regs1, mainInstructions.get(++pc1));
@ -482,7 +482,7 @@ public class PopulateDemoTrace extends GhidraScript {
* While this is a complicated call, there is nothing new to demonstrate in its
* implementation. As an exercise, see if you can follow what is happening within.
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap = trace.getTimeManager()
.createSnapshot("Stepped Thread 1: CALL clone -> Thread 2")
.getKey();
@ -520,7 +520,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* Hand emulate thread1 a few steps
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: RET from clone").getKey();
@ -535,7 +535,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* ...
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: TEST EAX,EAX").getKey();
@ -545,7 +545,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread1);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: JNZ child").getKey();
@ -557,7 +557,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* Switch to thread2
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: RET from clone").getKey();
@ -569,7 +569,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread2);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: TEST EAX,EAX").getKey();
@ -579,7 +579,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread2);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: JNZ child").getKey();
@ -591,7 +591,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* Switch to thread1
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: SUB RSP,0x10").getKey();
@ -603,7 +603,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread1);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: MOV...(1)").getKey();
@ -614,7 +614,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread1);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: MOV...(2)").getKey();
@ -625,7 +625,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread1);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: MOV...(3)").getKey();
@ -636,7 +636,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread1);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: MOV...(4)").getKey();
@ -650,7 +650,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* Switch to thread2
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: SUB RSP,0x10").getKey();
@ -662,7 +662,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread2);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: MOV...(1)").getKey();
@ -673,7 +673,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread2);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: MOV...(2)").getKey();
@ -684,7 +684,7 @@ public class PopulateDemoTrace extends GhidraScript {
placeRegUnits(snap, thread2);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: MOV...(3)").getKey();
@ -698,7 +698,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* Let thread2 exit first
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 2: CALL exit").getKey();
@ -708,7 +708,7 @@ public class PopulateDemoTrace extends GhidraScript {
/**
* Terminate
*/
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Step")) {
long snap =
trace.getTimeManager().createSnapshot("Stepped Thread 1: CALL exit").getKey();

View file

@ -70,7 +70,7 @@ public class StandAloneSyscallEmuExampleScript extends GhidraScript {
program =
new ProgramDB("syscall_example", language,
language.getCompilerSpecByID(new CompilerSpecID("gcc")), this);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
AddressSpace space = program.getAddressFactory().getDefaultAddressSpace();
entry = space.getAddress(0x00400000);
Address dataEntry = space.getAddress(0x00600000);

View file

@ -74,7 +74,7 @@ public class BreakpointLocationRow {
public void setName(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(loc.getTrace(), "Set breakpoint name", true)) {
UndoableTransaction.start(loc.getTrace(), "Set breakpoint name")) {
loc.setName(name);
}
}
@ -101,7 +101,7 @@ public class BreakpointLocationRow {
public void setComment(String comment) {
try (UndoableTransaction tid =
UndoableTransaction.start(loc.getTrace(), "Set breakpoint comment", true)) {
UndoableTransaction.start(loc.getTrace(), "Set breakpoint comment")) {
loc.setComment(comment);
}
}

View file

@ -830,7 +830,7 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
Program dest = getDestination().getOrCreateProgram(source, this);
boolean doRelease = !Arrays.asList(programManager.getAllOpenPrograms()).contains(dest);
TraceRecorder recorder = getRecorderIfEnabledAndReadsPresent();
try (UndoableTransaction tid = UndoableTransaction.start(dest, "Copy From Trace", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(dest, "Copy From Trace")) {
monitor.initialize(tableModel.getRowCount());
for (RangeEntry entry : tableModel.getModelData()) {
monitor.setMessage("Copying into " + entry.getDstRange());

View file

@ -35,9 +35,8 @@ public class RegionRow {
public void setName(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(region.getTrace(), "Renamed region", true)) {
UndoableTransaction.start(region.getTrace(), "Rename region")) {
region.setName(name);
tid.commit();
}
}
@ -76,7 +75,7 @@ public class RegionRow {
public void setRead(boolean read) {
try (UndoableTransaction tid =
UndoableTransaction.start(region.getTrace(), "Toggle region read flag", true)) {
UndoableTransaction.start(region.getTrace(), "Toggle region read flag")) {
region.setRead(read);
}
}
@ -87,7 +86,7 @@ public class RegionRow {
public void setWrite(boolean write) {
try (UndoableTransaction tid =
UndoableTransaction.start(region.getTrace(), "Toggle region write flag", true)) {
UndoableTransaction.start(region.getTrace(), "Toggle region write flag")) {
region.setWrite(write);
}
}
@ -98,7 +97,7 @@ public class RegionRow {
public void setExecute(boolean execute) {
try (UndoableTransaction tid =
UndoableTransaction.start(region.getTrace(), "Toggle region execute flag", true)) {
UndoableTransaction.start(region.getTrace(), "Toggle region execute flag")) {
region.setExecute(execute);
}
}
@ -109,7 +108,7 @@ public class RegionRow {
public void setVolatile(boolean vol) {
try (UndoableTransaction tid =
UndoableTransaction.start(region.getTrace(), "Toggle region volatile flag", true)) {
UndoableTransaction.start(region.getTrace(), "Toggle region volatile flag")) {
region.setVolatile(vol);
}
}

View file

@ -303,12 +303,10 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
// TODO: Action to adjust life span?
// Note: provider displays mappings for all time, so delete means delete, not truncate
try (UndoableTransaction tid =
UndoableTransaction.start(currentTrace, "Remove Static Mappings", false)) {
UndoableTransaction.start(currentTrace, "Remove Static Mappings")) {
for (StaticMappingRow mapping : ctx.getSelectedMappings()) {
mapping.getMapping().delete();
}
// TODO: Do I want all-or-nothing among all transactions?
tid.commit();
}
}

View file

@ -34,7 +34,7 @@ public class ModuleRow {
public void setName(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(module.getTrace(), "Renamed module", true)) {
UndoableTransaction.start(module.getTrace(), "Renamed module")) {
module.setName(name);
}
}

View file

@ -42,7 +42,7 @@ public class SectionRow {
public void setName(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(section.getTrace(), "Renamed section", true)) {
UndoableTransaction.start(section.getTrace(), "Rename section")) {
section.setName(name);
}
catch (DuplicateNameException e) {

View file

@ -229,7 +229,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
return null;
}
try (UndoableTransaction tid =
UndoableTransaction.start(current.getTrace(), "Resolve DataType", true)) {
UndoableTransaction.start(current.getTrace(), "Resolve DataType")) {
return current.getTrace().getDataTypeManager().resolve(dataType, null);
}
}

View file

@ -367,7 +367,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return null;
}
try (UndoableTransaction tid =
UndoableTransaction.start(currentTrace, "Resolve DataType", true)) {
UndoableTransaction.start(currentTrace, "Resolve DataType")) {
return currentTrace.getDataTypeManager().resolve(dataType, null);
}
}
@ -832,14 +832,13 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
*/
void writeRegisterDataType(Register register, DataType dataType) {
try (UndoableTransaction tid =
UndoableTransaction.start(current.getTrace(), "Edit Register Type", false)) {
UndoableTransaction.start(current.getTrace(), "Edit Register Type")) {
TraceCodeRegisterSpace space = getRegisterMemorySpace(true).getCodeSpace(true);
long snap = current.getViewSnap();
space.definedUnits().clear(Range.closed(snap, snap), register, TaskMonitor.DUMMY);
if (dataType != null) {
space.definedData().create(Range.atLeast(snap), register, dataType);
}
tid.commit();
}
catch (CodeUnitInsertionException | CancelledException e) {
throw new AssertionError(e);

View file

@ -79,8 +79,8 @@ public class StackFrameRow {
}
public void setComment(String comment) {
try (UndoableTransaction tid = UndoableTransaction
.start(frame.getStack().getThread().getTrace(), "Frame comment", true)) {
try (UndoableTransaction tid =
UndoableTransaction.start(frame.getStack().getThread().getTrace(), "Frame comment")) {
frame.setComment(getSnap(), comment);
}
}

View file

@ -44,7 +44,7 @@ public class ThreadRow {
public void setName(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(thread.getTrace(), "Renamed thread", true)) {
UndoableTransaction.start(thread.getTrace(), "Rename thread")) {
thread.setName(name);
}
}
@ -69,7 +69,7 @@ public class ThreadRow {
public void setComment(String comment) {
try (UndoableTransaction tid =
UndoableTransaction.start(thread.getTrace(), "Renamed thread", true)) {
UndoableTransaction.start(thread.getTrace(), "Rename thread")) {
thread.setComment(comment);
}
}

View file

@ -112,7 +112,7 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
if (dialog.isCanceled()) {
return;
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Rename Snapshot", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Rename Snapshot")) {
if (snapshot == null) {
snapshot = manager.getSnapshot(snap, true);
}

View file

@ -61,9 +61,8 @@ public class SnapshotRow {
public void setDescription(String description) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Modify snapshot description", false)) {
UndoableTransaction.start(trace, "Modify snapshot description")) {
snapshot.setDescription(description);
tid.commit();
}
}
}

View file

@ -208,7 +208,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
return null;
}
try (UndoableTransaction tid =
UndoableTransaction.start(currentTrace, "Resolve DataType", true)) {
UndoableTransaction.start(currentTrace, "Resolve DataType")) {
return currentTrace.getDataTypeManager().resolve(dataType, null);
}
}
@ -523,7 +523,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
}
}
try (UndoableTransaction tid =
UndoableTransaction.start(current.getTrace(), "Apply Watch Data Type", true)) {
UndoableTransaction.start(current.getTrace(), "Apply Watch Data Type")) {
try {
listing.clearCodeUnits(row.getAddress(), row.getRange().getMaxAddress(), false);
listing.createData(address, dataType, size);

View file

@ -55,9 +55,9 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe
@Override
public void addToTrace(long snap) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add guest " +
cSpec.getLanguage().getLanguageDescription() + "/" + cSpec.getCompilerSpecDescription(),
true)) {
String description = "Add guest " + cSpec.getLanguage().getLanguageDescription() + "/" +
cSpec.getCompilerSpecDescription();
try (UndoableTransaction tid = UndoableTransaction.start(trace, description)) {
TracePlatformManager platformManager = trace.getPlatformManager();
TraceGuestPlatform platform = platformManager.getOrAddGuestPlatform(cSpec);
if (platform == null) {

View file

@ -72,7 +72,7 @@ public class ObjectBasedDebuggerMemoryMapper implements DebuggerMemoryMapper {
protected AddressSpace createSpace(String name) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space for mapping", true)) {
UndoableTransaction.start(trace, "Create space for mapping")) {
AddressFactory factory = trace.getBaseAddressFactory();
AddressSpace space = factory.getAddressSpace(name);
if (space == null) {

View file

@ -128,9 +128,8 @@ public interface LogicalBreakpointInternal extends LogicalBreakpoint {
throw new IllegalStateException("Must save breakpoint to program before naming it");
}
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Rename breakpoint", false)) {
UndoableTransaction.start(program, "Rename breakpoint")) {
bookmark.set(bookmark.getCategory(), name);
tid.commit();
}
}
@ -154,8 +153,7 @@ public interface LogicalBreakpointInternal extends LogicalBreakpoint {
// volatile reads
Bookmark eBookmark = this.eBookmark;
Bookmark dBookmark = this.dBookmark;
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Clear breakpoint", false)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Clear breakpoint")) {
BookmarkManager bookmarkManager = program.getBookmarkManager();
if (eBookmark != null) {
bookmarkManager.removeBookmark(eBookmark);
@ -165,7 +163,6 @@ public interface LogicalBreakpointInternal extends LogicalBreakpoint {
}
// (e,d)Bookmark Gets nulled on program change callback
// If null here, logical breakpoint manager will get confused
tid.commit();
}
}
@ -250,7 +247,7 @@ public interface LogicalBreakpointInternal extends LogicalBreakpoint {
String delType =
enabled ? BREAKPOINT_DISABLED_BOOKMARK_TYPE : BREAKPOINT_ENABLED_BOOKMARK_TYPE;
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Enable breakpoint", true)) {
UndoableTransaction.start(program, "Enable breakpoint")) {
BookmarkManager manager = program.getBookmarkManager();
String catStr = computeCategory();
manager.setBookmark(address, addType, catStr, comment);

View file

@ -156,7 +156,7 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
long snap = coordinates.getViewSnap();
TraceMemoryOperations memOrRegs;
try (UndoableTransaction txid =
UndoableTransaction.start(trace, "Edit Variable", true)) {
UndoableTransaction.start(trace, "Edit Variable")) {
if (address.isRegisterAddress()) {
TraceThread thread = coordinates.getThread();
if (thread == null) {

View file

@ -105,7 +105,7 @@ public abstract class AbstractReadsTargetPcodeExecutorState
}
else {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space", true)) {
UndoableTransaction.start(trace, "Create space")) {
tms = TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, true);
}
}

View file

@ -403,7 +403,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
time.execute(trace, emu, monitor);
}
TraceSnapshot destSnap;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
destSnap = findScratch(trace, time);
emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false);
}

View file

@ -293,13 +293,12 @@ public enum ProgramEmulationUtils {
boolean success = false;
try {
trace = new DBTrace(getTraceName(program), program.getCompilerSpec(), consumer);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate", false)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
TraceSnapshot initial =
trace.getTimeManager().createSnapshot(EMULATION_STARTED_AT + pc);
long snap = initial.getKey();
loadExecutable(initial, program);
doLaunchEmulationThread(trace, snap, program, pc, pc);
tid.commit();
}
success = true;
return trace;
@ -339,9 +338,8 @@ public enum ProgramEmulationUtils {
public static TraceThread launchEmulationThread(Trace trace, long snap, Program program,
Address tracePc, Address programPc) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Emulate new Thread", false)) {
UndoableTransaction.start(trace, "Emulate new Thread")) {
TraceThread thread = doLaunchEmulationThread(trace, snap, program, tracePc, programPc);
tid.commit();
return thread;
}
}

View file

@ -21,7 +21,7 @@ import ghidra.util.database.UndoableTransaction;
public class RecorderPermanentTransaction implements AutoCloseable {
public static RecorderPermanentTransaction start(UndoableDomainObject obj, String description) {
UndoableTransaction tid = UndoableTransaction.start(obj, description, true);
UndoableTransaction tid = UndoableTransaction.start(obj, description);
return new RecorderPermanentTransaction(obj, tid);
}

View file

@ -255,7 +255,7 @@ public class TraceObjectManager {
TraceThread traceThread = threadRecorder.getTraceThread();
recorder.createSnapshot(traceThread + " started", traceThread, null);
try (UndoableTransaction tid =
UndoableTransaction.start(recorder.getTrace(), "Adjust thread creation", true)) {
UndoableTransaction.start(recorder.getTrace(), "Adjust thread creation")) {
long existing = traceThread.getCreationSnap();
if (existing == Long.MIN_VALUE) {
traceThread.setCreationSnap(recorder.getSnap());
@ -545,7 +545,7 @@ public class TraceObjectManager {
if (rec != null) {
String name = (String) added.get(TargetObject.DISPLAY_ATTRIBUTE_NAME);
try (UndoableTransaction tid =
UndoableTransaction.start(rec.getTrace(), "Renamed thread", true)) {
UndoableTransaction.start(rec.getTrace(), "Rename thread")) {
rec.getTraceThread().setName(name);
}
}

View file

@ -189,7 +189,7 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
String path = object.getJoinedPath(".");
// Don't offload, because we need a consistent map
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Object created: " + path, true)) {
UndoableTransaction.start(trace, "Object created: " + path)) {
objectRecorder.recordCreated(snap, object);
}
}

View file

@ -61,7 +61,7 @@ class ObjectRecorder {
this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty();
try (UndoableTransaction tid =
UndoableTransaction.start(recorder.trace, "Create root", true)) {
UndoableTransaction.start(recorder.trace, "Create root")) {
objectManager.createRootObject(schema);
}
}

View file

@ -730,7 +730,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
public void addMapping(TraceLocation from, ProgramLocation to, long length,
boolean truncateExisting) throws TraceConflictedMappingException {
try (UndoableTransaction tid =
UndoableTransaction.start(from.getTrace(), "Add mapping", true)) {
UndoableTransaction.start(from.getTrace(), "Add mapping")) {
DebuggerStaticMappingUtils.addMapping(from, to, length, truncateExisting);
}
}
@ -739,7 +739,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
public void addMapping(MapEntry<?, ?> entry, boolean truncateExisting)
throws TraceConflictedMappingException {
try (UndoableTransaction tid =
UndoableTransaction.start(entry.getFromTrace(), "Add mapping", true)) {
UndoableTransaction.start(entry.getFromTrace(), "Add mapping")) {
DebuggerStaticMappingUtils.addMapping(entry, truncateExisting);
}
}
@ -751,8 +751,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
entries.stream().collect(Collectors.groupingBy(ent -> ent.getFromTrace()));
for (Map.Entry<Trace, List<MapEntry<?, ?>>> ent : byTrace.entrySet()) {
Trace trace = ent.getKey();
try (UndoableTransaction tid =
UndoableTransaction.start(trace, description, true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, description)) {
doAddMappings(trace, ent.getValue(), monitor, truncateExisting);
}
}
@ -776,7 +775,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
public void addIdentityMapping(Trace from, Program toProgram, Range<Long> lifespan,
boolean truncateExisting) {
try (UndoableTransaction tid =
UndoableTransaction.start(from, "Add identity mappings", true)) {
UndoableTransaction.start(from, "Add identity mappings")) {
DebuggerStaticMappingUtils.addIdentityMapping(from, toProgram, lifespan,
truncateExisting);
}

View file

@ -226,7 +226,7 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
}
TraceData pcUnit = null;
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Disassemble: PC is code pointer", true)) {
UndoableTransaction.start(trace, "Disassemble: PC is code pointer")) {
TraceCodeRegisterSpace regCode =
codeManager.getCodeRegisterSpace(thread, frameLevel, true);
try {

View file

@ -98,7 +98,7 @@ public class DebuggerBreakpointMarkerPluginScreenShots extends GhidraScreenShotG
.getRootFolder()
.createFile("WinHelloCPP", program, TaskMonitor.DUMMY);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add Mapping", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add Mapping")) {
mappingService.addIdentityMapping(trace, program, Range.atLeast(0L), true);
}
waitForValue(() -> mappingService.getOpenMappedLocation(

View file

@ -130,12 +130,12 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
mb.testProcess1.addRegion("echo:.data", mb.rng(0x00600000, 0x00600fff), "rw");
mb.testProcess3.addRegion("echo:.text", mb.rng(0x7fac0000, 0x7fac0fff), "rx");
try (UndoableTransaction tid = UndoableTransaction.start(trace1, "Add mapping", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace1, "Add mapping")) {
DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace1, null, Range.atLeast(0L), addr(trace1, 0x00400000)),
new ProgramLocation(program, addr(program, 0x00400000)), 0x00210000, false);
}
try (UndoableTransaction tid = UndoableTransaction.start(trace3, "Add mapping", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace3, "Add mapping")) {
DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace3, null, Range.atLeast(0L), addr(trace3, 0x7fac0000)),
new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false);
@ -159,7 +159,7 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
trace3.getBreakpointManager()
.getBreakpointsAt(recorder3.getSnap(), addr(trace3, 0x7fac1234))));
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint")) {
program.getBookmarkManager()
.setBookmark(addr(program, 0x00401234),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1",

View file

@ -111,7 +111,7 @@ public class DebuggerCopyActionsPluginScreenShots extends GhidraScreenShotGenera
program = createDefaultProgram("echo", "Toy:BE:64:default", this);
AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add memory", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add memory")) {
program.setImageBase(tb.addr(stSpace, 0x00400000), true);
Memory memory = program.getMemory();
memory.createInitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x10000, (byte) 0,

View file

@ -103,7 +103,7 @@ public class DebuggerRegionsPluginScreenShots extends GhidraScreenShotGenerator
progBash = createDefaultProgram("bash", ProgramBuilder._X64, this);
progLibC = createDefaultProgram("libc.so.6", ProgramBuilder._X64, this);
try (UndoableTransaction tid = UndoableTransaction.start(progBash, "Add memory", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(progBash, "Add memory")) {
progBash.setImageBase(addr(progBash, 0x00400000), true);
progBash.getMemory()
.createInitializedBlock(".text", addr(progBash, 0x00400000), 0x10000, (byte) 0,
@ -113,7 +113,7 @@ public class DebuggerRegionsPluginScreenShots extends GhidraScreenShotGenerator
TaskMonitor.DUMMY, false);
}
try (UndoableTransaction tid = UndoableTransaction.start(progLibC, "Add memory", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(progLibC, "Add memory")) {
progLibC.setImageBase(addr(progLibC, 0x00400000), true);
progLibC.getMemory()
.createInitializedBlock(".text", addr(progLibC, 0x00400000), 0x10000, (byte) 0,

View file

@ -114,7 +114,7 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
progBash = createDefaultProgram("bash", ProgramBuilder._X64, this);
progLibC = createDefaultProgram("libc.so.6", ProgramBuilder._X64, this);
try (UndoableTransaction tid = UndoableTransaction.start(progBash, "Add memory", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(progBash, "Add memory")) {
progBash.setImageBase(addr(progBash, 0x00400000), true);
progBash.getMemory()
.createInitializedBlock(".text", addr(progBash, 0x00400000), 0x10000, (byte) 0,
@ -124,7 +124,7 @@ public class DebuggerModulesPluginScreenShots extends GhidraScreenShotGenerator
TaskMonitor.DUMMY, false);
}
try (UndoableTransaction tid = UndoableTransaction.start(progLibC, "Add memory", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(progLibC, "Add memory")) {
progLibC.setImageBase(addr(progLibC, 0x00400000), true);
progLibC.getMemory()
.createInitializedBlock(".text", addr(progLibC, 0x00400000), 0x10000, (byte) 0,

View file

@ -94,7 +94,7 @@ public class DebuggerStaticMappingPluginScreenShots extends GhidraScreenShotGene
progEcho = createDefaultProgram("bash", ProgramBuilder._X64, this);
progLibC = createDefaultProgram("libc.so.6", ProgramBuilder._X64, this);
try (UndoableTransaction tid = UndoableTransaction.start(progEcho, "Add memory", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(progEcho, "Add memory")) {
progEcho.setImageBase(addr(progEcho, 0x00400000), true);
progEcho.getMemory()
.createInitializedBlock(".text", addr(progEcho, 0x00400000), 0x10000, (byte) 0,
@ -104,7 +104,7 @@ public class DebuggerStaticMappingPluginScreenShots extends GhidraScreenShotGene
TaskMonitor.DUMMY, false);
}
try (UndoableTransaction tid = UndoableTransaction.start(progLibC, "Add memory", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(progLibC, "Add memory")) {
progLibC.setImageBase(addr(progLibC, 0x00400000), true);
progLibC.getMemory()
.createInitializedBlock(".text", addr(progLibC, 0x00400000), 0x10000, (byte) 0,

View file

@ -83,7 +83,7 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator {
public void testCaptureDebuggerStackPlugin() throws Throwable {
DomainFolder root = tool.getProject().getProjectData().getRootFolder();
program = createDefaultProgram("echo", ToyProgramBuilder._X64, this);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Populate", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Populate")) {
program.setImageBase(addr(program, 0x00400000), true);
program.getMemory()
.createInitializedBlock(".text", addr(program, 0x00400000), 0x10000, (byte) 0,

View file

@ -72,7 +72,7 @@ public class DebuggerThreadsPluginScreenShots extends GhidraScreenShotGenerator
waitForValue(() -> recorder.getTraceThread(handler2Thread));
AbstractGhidraHeadedDebuggerGUITest.waitForDomainObject(trace);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Comments", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Comments")) {
recorder.getTraceThread(mainThread).setComment("GUI main loop");
recorder.getTraceThread(serverThread).setComment("Server");
recorder.getTraceThread(handler1Thread).setComment("Handler 1");

View file

@ -736,7 +736,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
program = new ProgramDB("static-" + name.getMethodName(), lang,
lang.getDefaultCompilerSpec(), this);
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Set Executable Path", true)) {
UndoableTransaction.start(program, "Set Executable Path")) {
program.setExecutablePath(path);
}
programManager.openProgram(program);

View file

@ -107,7 +107,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
protected void addStaticMemoryAndBreakpoint() throws LockException, DuplicateNameException,
MemoryConflictException, AddressOverflowException, CancelledException {
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Add bookmark break", true)) {
UndoableTransaction.start(program, "Add bookmark break")) {
program.getMemory()
.createInitializedBlock(".text", addr(program, 0x00400000), 0x1000, (byte) 0,
TaskMonitor.DUMMY, false);
@ -118,7 +118,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
}
protected void addMapping(Trace trace) throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping")) {
DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550123)),
new ProgramLocation(program, addr(program, 0x00400123)), 0x1000, false);
@ -421,7 +421,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
}
waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size()));
try (UndoableTransaction tid = UndoableTransaction.start(program, "Disassemble", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Disassemble")) {
Disassembler.getDisassembler(program, TaskMonitor.DUMMY, msg -> {
}).disassemble(addr(program, 0x00400123), set(rng(program, 0x00400123, 0x00400123)));
}
@ -449,7 +449,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
}
waitForPass(() -> assertEquals(0, breakpointService.getAllBreakpoints().size()));
try (UndoableTransaction tid = UndoableTransaction.start(program, "Disassemble", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Disassemble")) {
program.getListing().createData(addr(program, 0x00400123), ByteDataType.dataType);
}
waitForDomainObject(program);

View file

@ -81,7 +81,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
}
protected void addMapping(Trace trace, Program prog) throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add mapping")) {
DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(trace, null, Range.atLeast(0L), addr(trace, 0x55550000)),
new ProgramLocation(prog, addr(prog, 0x00400000)), 0x1000, false);
@ -103,7 +103,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
protected void addStaticMemoryAndBreakpoint() throws LockException, DuplicateNameException,
MemoryConflictException, AddressOverflowException, CancelledException {
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Add bookmark break", true)) {
UndoableTransaction.start(program, "Add bookmark break")) {
program.getMemory()
.createInitializedBlock(".text", addr(program, 0x00400000), 0x1000, (byte) 0,
TaskMonitor.DUMMY, false);

View file

@ -136,7 +136,7 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerG
AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add blocks", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add blocks")) {
program.getMemory()
.createInitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x8000, (byte) 0,
monitor, false);
@ -230,7 +230,7 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerG
AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace();
MemoryBlock block;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Create block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Create block")) {
block = program.getMemory()
.createUninitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x10000,
false);
@ -292,7 +292,7 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerG
AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace();
MemoryBlock block;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Create block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Create block")) {
block = program.getMemory()
.createUninitializedBlock(".text", tb.addr(stSpace, 0x00400000), 0x10000,
false);

View file

@ -84,7 +84,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
Address paddr = tb.addr(stSpace, 0x00400000);
assertTrue(AllCopiers.BYTES.isRequiresInitializedMemory());
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
false);
@ -117,7 +117,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
Address paddr = tb.addr(stSpace, 0x00400000);
assertFalse(AllCopiers.STATE.isRequiresInitializedMemory());
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
false);
@ -194,7 +194,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
assertTrue(iit.hasNext());
}
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
Address paddr = tb.addr(stSpace, 0x00400000);
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
@ -242,7 +242,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
assertTrue(iit.hasNext());
}
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
Address paddr = tb.addr(stSpace, 0x00400000);
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
@ -315,7 +315,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
assertFalse(insCtx.equals(tb.trace.getRegisterContextManager()
.getDefaultValue(tb.language, contextReg, checkCtx.getAddress())));
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
Address paddr = tb.addr(stSpace, 0x00400000);
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
@ -372,7 +372,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
tb.buf(0x00, 0x03, 0x00, 0x01, 0x02));
}
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
Address paddr = tb.addr(stSpace, 0x00600000);
program.getMemory()
.createInitializedBlock(".data", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
@ -432,7 +432,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
tb.buf(0x00, 0x03, 0x00, 0x01, 0x02));
}
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
Address paddr = tb.addr(stSpace, 0x00600000);
program.getMemory()
.createInitializedBlock(".data", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
@ -505,7 +505,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
}
Address paddr = tb.addr(stSpace, 0x00400000);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Copy")) {
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000, (byte) 0, TaskMonitor.DUMMY,
false);
@ -570,7 +570,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
}
Address paddr = tb.addr(stSpace, 0x55550000);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000,
(byte) 0, TaskMonitor.DUMMY, false);
@ -621,7 +621,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
}
Address paddr = tb.addr(stSpace, 0x55550000);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000,
(byte) 0, TaskMonitor.DUMMY, false);
@ -677,7 +677,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
}
Address paddr = tb.addr(stSpace, 0x55550000);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000,
(byte) 0, TaskMonitor.DUMMY, false);
@ -720,7 +720,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
}
Address paddr = tb.addr(stSpace, 0x55550000);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Init")) {
program.getMemory()
.createInitializedBlock(".text", paddr, 0x10000,
(byte) 0, TaskMonitor.DUMMY, false);

View file

@ -115,7 +115,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
intoProject(program);
AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
program.getMemory()
.createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0,
monitor, false);
@ -488,7 +488,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
intoProject(program);
AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
program.getMemory()
.createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0,
monitor, false);
@ -601,7 +601,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
intoProject(program);
AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
program.getMemory()
.createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0,
monitor, false);
@ -1486,7 +1486,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
intoProject(program);
AddressSpace ss = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
program.getMemory()
.createInitializedBlock(".text", ss.getAddress(0x00600000), 0x10000, (byte) 0,
monitor, false);

View file

@ -77,7 +77,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
protected void addBlocks() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
Memory mem = program.getMemory();
blockExeText = mem.createInitializedBlock(".text", tb.addr(0x00400000), 0x100, (byte) 0,
monitor, false);
@ -257,7 +257,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(provider.actionMapRegions.isEnabled());
addBlocks();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
program.setName("echo");
}
waitForDomainObject(program);

View file

@ -39,7 +39,7 @@ public class ModelQueryTest extends AbstractGhidraHeadedDebuggerGUITest {
ModelQuery rootQuery = ModelQuery.parse("");
ModelQuery threadQuery = ModelQuery.parse("Processes[].Threads[]");
try (UndoableTransaction tid = UndoableTransaction.start(tb.trace, "Init", true)) {
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceObjectManager objects = tb.trace.getObjectManager();
TraceObjectValue rootVal =

View file

@ -129,7 +129,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
protected MemoryBlock addBlock() throws Exception,
MemoryConflictException, AddressOverflowException, CancelledException {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
return program.getMemory()
.createInitializedBlock(".text", tb.addr(0x00400000), 0x1000, (byte) 0, monitor,
false);
@ -226,7 +226,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForSwing();
MemoryBlock block = addBlock();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
program.setName(modExe.getName());
}
waitForDomainObject(program);
@ -355,7 +355,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertTrue(modulesProvider.actionMapIdentically.isEnabled());
// Need some substance in the program
try (UndoableTransaction tid = UndoableTransaction.start(program, "Populate", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Populate")) {
addBlock();
}
waitForDomainObject(program);
@ -390,7 +390,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Still
assertFalse(modulesProvider.actionMapModules.isEnabled());
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
program.setImageBase(addr(program, 0x00400000), true);
program.setName(modExe.getName());
@ -458,7 +458,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(modulesProvider.actionMapSections.isEnabled());
MemoryBlock block = addBlock();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Change name")) {
program.setName(modExe.getName());
}
waitForDomainObject(program);

View file

@ -123,7 +123,7 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
}
waitForDomainObject(tb.trace);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
program.getMemory()
.createInitializedBlock(".text", addr(program, 0xc0de1234L), 0x100, (byte) 0,
TaskMonitor.DUMMY, false);

View file

@ -478,7 +478,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
assertProviderPopulated();
Function func;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add Function", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add Function")) {
program.getMemory()
.createInitializedBlock(".text", addr(program, 0x00600000), 0x1000, (byte) 0,
TaskMonitor.DUMMY, false);

View file

@ -536,7 +536,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
programManager.openProgram(program);
AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block")) {
Memory mem = program.getMemory();
mem.createInitializedBlock(".data", tb.addr(stSpace, 0x00600000), 0x10000,
(byte) 0, TaskMonitor.DUMMY, false);
@ -617,7 +617,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
structDt.add(DWordDataType.dataType, "field0", "");
structDt.add(DWordDataType.dataType, "field4", "");
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add data", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add data")) {
program.getListing().createData(tb.addr(stSpace, 0x00600000), structDt);
}
@ -658,7 +658,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
setupMappedDataSection();
Symbol symbol;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add symbol", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add symbol")) {
symbol = program.getSymbolTable()
.createLabel(tb.addr(0x00601234), "my_symbol", SourceType.USER_DEFINED);
}

View file

@ -256,7 +256,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
protected void addProgramTextBlock(Program p) throws Throwable {
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Add .text block", true)) {
UndoableTransaction.start(program, "Add .text block")) {
p.getMemory()
.createInitializedBlock(".text", addr(p, 0x00400000), 0x1000, (byte) 0,
monitor, false);
@ -281,7 +281,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
TraceMemoryRegion textRegion =
waitFor(() -> r.getTraceMemoryRegion(region), "Recorder missed region: " + region);
try (UndoableTransaction tid =
UndoableTransaction.start(t, "Add .text mapping", true)) {
UndoableTransaction.start(t, "Add .text mapping")) {
DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(t, null, textRegion.getLifespan(),
textRegion.getMinAddress()),
@ -292,7 +292,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
protected void removeTextMapping(TraceRecorder r, Program p) throws Throwable {
Trace t = r.getTrace();
try (UndoableTransaction tid = UndoableTransaction.start(t, "Remove .text mapping", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(t, "Remove .text mapping")) {
TraceStaticMapping mapping =
t.getStaticMappingManager().findContaining(addr(t, 0x55550000), r.getSnap());
mapping.delete();
@ -341,7 +341,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
}
protected void addProgramBreakpoints(Program p) throws Throwable {
try (UndoableTransaction tid = UndoableTransaction.start(p, "Create bookmarks", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(p, "Create bookmarks")) {
enBm = p.getBookmarkManager()
.setBookmark(addr(p, 0x00400123),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1", "");
@ -362,7 +362,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
}
protected void removeProgramBreakpoints(Program p) throws Throwable {
try (UndoableTransaction tid = UndoableTransaction.start(p, "Remove breakpoints", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(p, "Remove breakpoints")) {
p.getBookmarkManager().removeBookmark(enBm);
p.getBookmarkManager().removeBookmark(disBm);
}
@ -1160,12 +1160,13 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
expectMappingChange(() -> addTextMapping(recorder1, text, program));
waitForSwing();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will abort", false)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will abort")) {
addTargetSoftwareBreakpoint(recorder1, text);
waitForDomainObject(trace);
// Sanity
assertLogicalBreakpointForMappedSoftwareBreakpoint(trace);
tid.abort();
}
waitForDomainObject(trace);
@ -1197,7 +1198,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
waitForDomainObject(trace);
changeListener.assertAgreesWithService();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will abort", false)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will abort")) {
expectMappingChange(() -> addTextMapping(recorder1, text, program));
waitForSwing();
@ -1226,7 +1227,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
addProgramTextBlock(program);
TestTargetMemoryRegion text = addTargetTextRegion(mb.testProcess1);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will abort", false)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will abort")) {
addTargetSoftwareBreakpoint(recorder1, text);
expectMappingChange(() -> addTextMapping(recorder1, text, program));
@ -1260,12 +1261,13 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
expectMappingChange(() -> addTextMapping(recorder1, text, program));
waitForSwing();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Will abort", false)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Will abort")) {
addProgramBreakpoints(program);
waitForDomainObject(program);
// Sanity
assertLogicalBreakpointsForMappedBookmarks(trace);
tid.abort();
}
waitForDomainObject(program);
@ -1285,7 +1287,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
addProgramTextBlock(program);
TestTargetMemoryRegion text = addTargetTextRegion(mb.testProcess1);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will undo", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Will undo")) {
addTargetSoftwareBreakpoint(recorder1, text);
expectMappingChange(() -> addTextMapping(recorder1, text, program));
}
@ -1321,7 +1323,7 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
expectMappingChange(() -> addTextMapping(recorder1, text, program));
waitForSwing();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Will undo", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Will undo")) {
addProgramBreakpoints(program);
}
waitForDomainObject(program);

View file

@ -66,7 +66,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
Register regPC = program.getRegister("pc");
Register regR0 = program.getRegister("r0");
Register regR1 = program.getRegister("r1");
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
@ -122,7 +122,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
Register regPC = program.getRegister("pc");
Register regR0 = program.getRegister("r0");
Register regR1 = program.getRegister("r1");
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
@ -181,7 +181,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
Register regPC = program.getRegister("PC");
Register regW0 = program.getRegister("W0");
Register regW1 = program.getRegister("W1");
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);

View file

@ -77,7 +77,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
protected TraceMemoryRegisterSpace createRegSpace(TraceThread thread) {
try (UndoableTransaction tid =
UndoableTransaction.start(thread.getTrace(), "Create register space", true)) {
UndoableTransaction.start(thread.getTrace(), "Create register space")) {
return thread.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, true);
}
}
@ -143,7 +143,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
r -> r.isBaseRegister() && r != pc && r != sp);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type")) {
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
code.definedData().create(Range.atLeast(0L), pc, PointerDataType.dataType);
}
@ -192,7 +192,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
r -> r.isBaseRegister() && r != pc && r != sp);
TestTargetRegisterBankInThread regs = mb.testThread1.addRegisterBank();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add SP type", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add SP type")) {
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
code.definedData().create(Range.atLeast(0L), sp, PointerDataType.dataType);
}
@ -241,7 +241,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes
//waitForCondition(() -> registerMapped(recorder, thread, pc));
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add PC type")) {
TraceCodeRegisterSpace code = trace.getCodeManager().getCodeRegisterSpace(thread, true);
code.definedData().create(Range.atLeast(0L), pc, PointerDataType.dataType);
}

View file

@ -155,10 +155,9 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
this.baseAddressFactory =
new TraceAddressFactory(this.baseLanguage, this.baseCompilerSpec);
try (UndoableTransaction tid = UndoableTransaction.start(this, "Create", false)) {
try (UndoableTransaction tid = UndoableTransaction.start(this, "Create")) {
initOptions(DBOpenMode.CREATE);
init();
tid.commit();
}
catch (VersionException | CancelledException e) {
throw new AssertionError(e);

View file

@ -60,7 +60,7 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
* @param <OT> the type of object containing the field
*/
public static class AddressDBFieldCodec<OT extends DBAnnotatedObject & DecodesAddresses>
extends AbstractDBFieldCodec<Address, OT, BinaryField> {
extends AbstractDBFieldCodec<Address, OT, FixedField10> {
static final Charset UTF8 = Charset.forName("UTF-8");
public static byte[] encode(Address address) {
@ -68,16 +68,8 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
return null;
}
AddressSpace as = address.getAddressSpace();
ByteBuffer buf = ByteBuffer.allocate(Byte.BYTES + Short.BYTES + Long.BYTES);
if (as instanceof OverlayAddressSpace) {
buf.put((byte) 1);
OverlayAddressSpace os = (OverlayAddressSpace) as;
buf.putShort((short) os.getDatabaseKey());
}
else {
buf.put((byte) 0);
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES + Long.BYTES);
buf.putShort((short) as.getSpaceID());
}
buf.putLong(address.getOffset());
return buf.array();
}
@ -86,29 +78,19 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
if (enc == null) {
return null;
}
else {
ByteBuffer buf = ByteBuffer.wrap(enc);
byte overlay = buf.get();
final AddressSpace as;
if (overlay == 1) {
short key = buf.getShort();
as = osa.spacesByKey.get(key & 0xffffL);
}
else {
short id = buf.getShort();
as = osa.trace.getInternalAddressFactory().getAddressSpace(id);
}
final AddressSpace as = osa.trace.getInternalAddressFactory().getAddressSpace(id);
long offset = buf.getLong();
return as.getAddress(offset);
}
}
public AddressDBFieldCodec(Class<OT> objectType, Field field, int column) {
super(Address.class, objectType, BinaryField.class, field, column);
super(Address.class, objectType, FixedField10.class, field, column);
}
@Override
public void store(Address value, BinaryField f) {
public void store(Address value, FixedField10 f) {
f.setBinaryData(encode(value));
}

View file

@ -43,8 +43,8 @@ import ghidra.trace.database.address.DBTraceOverlaySpaceAdapter.AddressDBFieldCo
import ghidra.trace.database.address.DBTraceOverlaySpaceAdapter.DecodesAddresses;
import ghidra.trace.database.data.DBTraceDataTypeManager;
import ghidra.trace.database.guest.DBTraceGuestPlatform;
import ghidra.trace.database.guest.DBTracePlatformManager;
import ghidra.trace.database.guest.DBTraceGuestPlatform.DBTraceGuestLanguage;
import ghidra.trace.database.guest.DBTracePlatformManager;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager;
import ghidra.trace.database.space.DBTraceDelegatingManager;
@ -69,7 +69,17 @@ public class DBTraceCodeManager
implements TraceCodeManager, DBTraceDelegatingManager<DBTraceCodeSpace> {
public static final String NAME = "Code";
@DBAnnotatedObjectInfo(version = 0)
/**
* A prototype entry
*
* <p>
* Version history:
* <ul>
* <li>1: Change {@link #address} to 10-byte fixed encoding</li>
* <li>0: Initial version and previous unversioned implementation</li>
* </ul>
*/
@DBAnnotatedObjectInfo(version = 1)
public static class DBTraceCodePrototypeEntry extends DBAnnotatedObject
implements DecodesAddresses {
public static final String TABLE_NAME = "Prototypes";
@ -99,7 +109,7 @@ public class DBTraceCodeManager
@DBAnnotatedField(column = CONTEXT_COLUMN_NAME)
private byte[] context;
@DBAnnotatedField(column = ADDRESS_COLUMN_NAME, codec = AddressDBFieldCodec.class)
private Address address;
private Address address = Address.NO_ADDRESS;
@DBAnnotatedField(column = DELAY_COLUMN_NAME)
private boolean delaySlot;

View file

@ -34,7 +34,17 @@ import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@DBAnnotatedObjectInfo(version = 0)
/**
* The implementation of a stack mapping, directly via a database object
*
* <p>
* Version history:
* <ul>
* <li>1: Change {@link #traceAddress} to 10-byte fixed encoding</li>
* <li>0: Initial version and previous unversioned implementation</li>
* </ul>
*/
@DBAnnotatedObjectInfo(version = 1)
public class DBTraceStaticMapping extends DBAnnotatedObject
implements TraceStaticMapping, DecodesAddresses {
public static final String TABLE_NAME = "StaticMappings";
@ -80,7 +90,7 @@ public class DBTraceStaticMapping extends DBAnnotatedObject
column = TRACE_ADDRESS_COLUMN_NAME,
indexed = true,
codec = AddressDBFieldCodec.class)
private Address traceAddress;
private Address traceAddress = Address.NO_ADDRESS;
@DBAnnotatedField(column = LENGTH_COLUMN_NAME)
private long length;
@DBAnnotatedField(column = START_SNAP_COLUMN_NAME)

View file

@ -33,7 +33,17 @@ import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@DBAnnotatedObjectInfo(version = 0)
/**
* The implementation of a stack frame, directly via a database object
*
* <p>
* Version history:
* <ul>
* <li>1: Change {@link #pc} to 10-byte fixed encoding, make it sparse, so optional</li>
* <li>0: Initial version and previous unversioned implementations</li>
* </ul>
*/
@DBAnnotatedObjectInfo(version = 1)
public class DBTraceStackFrame extends DBAnnotatedObject
implements TraceStackFrame, DecodesAddresses {
public static final String TABLE_NAME = "StackFrames";
@ -56,7 +66,11 @@ public class DBTraceStackFrame extends DBAnnotatedObject
private long stackKey;
@DBAnnotatedField(column = LEVEL_COLUMN_NAME)
private int level;
@DBAnnotatedField(column = PC_COLUMN_NAME, indexed = true, codec = AddressDBFieldCodec.class)
@DBAnnotatedField(
column = PC_COLUMN_NAME,
indexed = true,
codec = AddressDBFieldCodec.class,
sparse = true)
private Address pc;
@DBAnnotatedField(column = COMMENT_COLUMN_NAME)
private String comment;

View file

@ -55,7 +55,17 @@ import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
@DBAnnotatedObjectInfo(version = 0)
/**
* The implementation of a function symbol, directly via a database object
*
* <p>
* Version history:
* <ul>
* <li>1: Change {link #entryPoint} to 10-byte fixed encoding</li>
* <li>0: Initial version and previous unversioned implementation</li>
* </ul>
*/
@DBAnnotatedObjectInfo(version = 1)
public class DBTraceFunctionSymbol extends DBTraceNamespaceSymbol
implements TraceFunctionSymbol, DecodesAddresses {
@SuppressWarnings("hiding")
@ -103,8 +113,9 @@ public class DBTraceFunctionSymbol extends DBTraceNamespaceSymbol
@DBAnnotatedColumn(STACK_RETURN_OFFSET_COLUMN_NAME)
static DBObjectColumn STACK_RETURN_OFFSET_COLUMN;
// Do I need to index entry, too? Not just body?
@DBAnnotatedField(column = ENTRY_COLUMN_NAME, codec = AddressDBFieldCodec.class)
protected Address entryPoint; // Do I need to index entry, too? Not just body?
protected Address entryPoint = Address.NO_ADDRESS;
@DBAnnotatedField(column = START_SNAP_COLUMN_NAME)
protected long startSnap;
@DBAnnotatedField(column = END_SNAP_COLUMN_NAME)

View file

@ -39,7 +39,17 @@ import ghidra.util.database.DBObjectColumn;
import ghidra.util.database.annot.*;
import ghidra.util.exception.DuplicateNameException;
@DBAnnotatedObjectInfo(version = 0)
/**
* The implementation of a label symbol, directly via a database object
*
* <p>
* Version history:
* <ul>
* <li>1: Change {@link #address} to 10-byte fixed encoding</li>
* <li>0: Initial version and previous unversioned implementation</li>
* </ul>
*/
@DBAnnotatedObjectInfo(version = 1)
public class DBTraceLabelSymbol extends AbstractDBTraceSymbol
implements TraceLabelSymbol, DBTraceSpaceKey, DecodesAddresses {
static final String TABLE_NAME = "Labels";
@ -61,8 +71,9 @@ public class DBTraceLabelSymbol extends AbstractDBTraceSymbol
@DBAnnotatedColumn(END_SNAP_COLUMN_NAME)
static DBObjectColumn END_SNAP_COLUMN;
// NOTE: Indexed in manager's range map
@DBAnnotatedField(column = ADDRESS_COLUMN_NAME, codec = AddressDBFieldCodec.class)
protected Address address; // NOTE: Indexed in manager's range map
protected Address address = Address.NO_ADDRESS;
@DBAnnotatedField(column = THREAD_COLUMN_NAME)
protected long threadKey;
@DBAnnotatedField(column = START_SNAP_COLUMN_NAME)

View file

@ -91,7 +91,17 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
protected abstract DBTraceReference construct(DBTraceReferenceEntry ent);
}
@DBAnnotatedObjectInfo(version = 0)
/**
* A reference entry
*
* <p>
* Version history:
* <ul>
* <li>1: Change {@link #toAddress} to 10-byte fixed encoding</li>
* <li>0: Initial version and previous unversioned implementation</li>
* </ul>
*/
@DBAnnotatedObjectInfo(version = 1)
protected static class DBTraceReferenceEntry
extends AbstractDBTraceAddressSnapRangePropertyMapData<DBTraceReferenceEntry>
implements DecodesAddresses {
@ -137,7 +147,7 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
column = TO_ADDR_COLUMN_NAME,
indexed = true,
codec = AddressDBFieldCodec.class)
protected Address toAddress;
protected Address toAddress = Address.NO_ADDRESS;
@DBAnnotatedField(column = SYMBOL_ID_COLUMN_NAME, indexed = true)
protected long symbolId; // TODO: Is this at the from or to address? I think TO...
@DBAnnotatedField(column = REF_TYPE_COLUMN_NAME, codec = RefTypeDBFieldCodec.class)

View file

@ -193,7 +193,7 @@ public class ToyDBTraceBuilder implements AutoCloseable {
}
public UndoableTransaction startTransaction() {
return UndoableTransaction.start(trace, "Testing", true);
return UndoableTransaction.start(trace, "Testing");
}
public DBTraceBookmarkType getOrAddBookmarkType(String name) {

View file

@ -80,7 +80,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
@Test
public void testSetName() throws InvalidNameException {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.setName("Another name");
}
assertEquals("Another name", trace.getName());
@ -93,12 +93,12 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
Path tmpDir = Files.createTempDirectory("test");
File archiveFile = tmpDir.resolve("test.gdt").toFile();
FileDataTypeManager dtm2 = FileDataTypeManager.createFileArchive(archiveFile);
try (UndoableTransaction tid = UndoableTransaction.start(dtm2, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(dtm2, "Testing")) {
dtm2.addDataType(mine, DataTypeConflictHandler.DEFAULT_HANDLER);
}
DataType got = dtm2.getDataType(minePath);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.addDataType(got, DataTypeConflictHandler.DEFAULT_HANDLER);
}
dtm2.delete();
@ -112,7 +112,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
public void testAddAndGet() {
StructureDataType mine = getTestDataType();
DataTypePath minePath = mine.getDataTypePath();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.addDataType(mine, DataTypeConflictHandler.REPLACE_HANDLER);
}
@ -125,14 +125,14 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
public void testAddRemoveUndoThenGet() throws IOException {
StructureDataType mine = getTestDataType();
DataTypePath minePath = mine.getDataTypePath();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.addDataType(mine, DataTypeConflictHandler.REPLACE_HANDLER);
}
DataType got = dtm.getDataType(minePath);
assertEquals(mine.toString(), got.toString()); // TODO: Eww
try (UndoableTransaction tid = UndoableTransaction.start(trace, "To Undo", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "To Undo")) {
dtm.remove(got, new ConsoleTaskMonitor());
}
@ -148,7 +148,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
public void testChangeDataType() {
StructureDataType mine = getTestDataType();
DataTypePath minePath = mine.getDataTypePath();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.addDataType(mine, DataTypeConflictHandler.REPLACE_HANDLER);
Structure got = (Structure) dtm.getDataType(minePath);
@ -165,7 +165,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
DataTypePath mineAPath = mineA.getDataTypePath();
StructureDataType mineB = getTestDataTypeB();
DataTypePath mineBPath = mineB.getDataTypePath();
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.addDataType(mineA, DataTypeConflictHandler.REPLACE_HANDLER);
DataType got = dtm.getDataType(mineAPath);
@ -181,7 +181,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
StructureDataType mine = getTestDataType();
DataTypePath minePath = mine.getDataTypePath();
DataType got;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.addDataType(mine, DataTypeConflictHandler.REPLACE_HANDLER);
got = dtm.getDataType(minePath);
@ -197,7 +197,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
StructureDataType mine = getTestDataType();
DataTypePath minePath = mine.getDataTypePath();
DataType got;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.addDataType(mine, DataTypeConflictHandler.REPLACE_HANDLER);
got = dtm.getDataType(minePath);
@ -211,7 +211,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
@Test
public void testCreateCategory() {
Category category;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
category = dtm.createCategory(new CategoryPath("/Another/Path"));
}
assertEquals(category, dtm.getCategory(new CategoryPath("/Another/Path")));
@ -220,7 +220,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
@Test
public void testMoveCategory() throws DuplicateNameException {
Category toMove;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
Category category = dtm.createCategory(new CategoryPath("/Another/Path"));
toMove = dtm.createCategory(new CategoryPath("/MoveMe"));
category.moveCategory(toMove, new ConsoleTaskMonitor());
@ -231,7 +231,7 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
@Test
public void testRenameCategory() throws DuplicateNameException, InvalidNameException {
Category category;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
category = dtm.createCategory(new CategoryPath("/Another/Path"));
category.setName("Renamed");
}
@ -241,12 +241,12 @@ public class DBTraceDataTypeManagerTest extends AbstractGhidraHeadlessIntegratio
@Test
public void testRemoveCategory() {
Category category;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
category = dtm.createCategory(new CategoryPath("/Another/Path"));
}
assertEquals(category, dtm.getCategory(new CategoryPath("/Another/Path")));
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Testing")) {
dtm.getCategory(new CategoryPath("/Another"))
.removeEmptyCategory("Path",
new ConsoleTaskMonitor());

View file

@ -145,7 +145,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
.getLanguage(new LanguageID("Toy:BE:64:default"));
obj = new MyObject(this);
factory = new DBCachedObjectStoreFactory(obj);
try (UndoableTransaction tid = UndoableTransaction.start(obj, "CreateTable", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "CreateTable")) {
space = new DBTraceAddressSnapRangePropertyMapSpace<>("Entries", factory,
obj.getReadWriteLock(), toy.getDefaultSpace(), MyEntry.class, MyEntry::new);
}
@ -163,7 +163,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
view = makeIntersectingView(tasr(0x0100, 0x2fff, 0, 0), s -> true);
assertFalse(view.contains(addr(0x1800)));
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 9), "A");
}
@ -195,7 +195,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
view = makeIntersectingView(tasr(0x0100, 0x2fff, 0, 0), s -> true);
assertFalse(view.contains(addr(0x1000), addr(0x1fff)));
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x3fff, 1, 1), "B");
}
@ -232,7 +232,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
view = makeIntersectingView(tasr(0x0100, 0x2fff, 0, 0), s -> true);
assertFalse(view.contains(set(rng(0x1000, 0x1fff))));
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x3fff, 1, 1), "B");
}
@ -271,7 +271,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
assertEquals(0, view.getNumAddressRanges());
assertEquals(0, view.getNumAddresses());
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -307,7 +307,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
assertNull(view.getMinAddress());
assertNull(view.getMaxAddress());
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x3fff, 1, 1), "B");
}
@ -337,7 +337,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
view = makeIntersectingView(tasr(0x0100, 0x2fff, 0, 0), s -> true);
assertEquals(List.of(), list(view.iterator()));
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -376,7 +376,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
view = makeIntersectingView(tasr(0x0100, 0x2fff, 0, 0), s -> true);
assertEquals(List.of(), list(view.getAddresses(true)));
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(1, 5, 0, 0), "A");
}
@ -396,7 +396,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testIntersects() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -418,7 +418,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testUnion() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -431,7 +431,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testSubtract() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -445,7 +445,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testXor() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -459,7 +459,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testHasSameAddresses() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -475,7 +475,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testGetFirstLastRanges() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x27ff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -489,7 +489,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testGetRangeContaining() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");
@ -509,7 +509,7 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetViewTest
@Test
public void testFindFirstAddressInCommon() {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction trans = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 0, 0), "A");
space.put(tasr(0x2000, 0x2fff, 1, 1), "B");

View file

@ -157,7 +157,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterableTest
new LanguageID("Toy:BE:64:default"));
obj = new MyObject(this);
factory = new DBCachedObjectStoreFactory(obj);
try (UndoableTransaction tid = UndoableTransaction.start(obj, "CreateTable", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "CreateTable")) {
space = new DBTraceAddressSnapRangePropertyMapSpace<>("Entries", factory,
obj.getReadWriteLock(), toy.getDefaultSpace(), MyEntry.class, MyEntry::new);
}
@ -188,7 +188,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterableTest
@Test
public void testOutOfWindow() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 5, 10), "A");
}
@ -208,7 +208,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterableTest
@Test
public void testSingleEntry() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 90, 95), "A");
}
@ -232,7 +232,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterableTest
@Test
public void testEntriesAtExtremes() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x0000, 0x0fff, 5, 10), "W");
space.put(tasr(-0x1000, -0x0001, 5, 10), "E");
space.put(tasr(0x1000, 0x1fff, Long.MIN_VALUE, Long.MIN_VALUE + 10), "S");
@ -240,18 +240,18 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterableTest
}
it = makeOcclusionIterable(tasr(0x0000, -0x0001, Long.MIN_VALUE, Long.MAX_VALUE));
assertEquals(list( //
ent(0x0000, 0x0fff, 5, 10, "W"), //
ent(0x1000, 0x1fff, Long.MIN_VALUE, Long.MIN_VALUE + 10, "S"), //
ent(0x2000, 0x2fff, Long.MAX_VALUE - 10, Long.MAX_VALUE, "N"), //
ent(-0x1000, -0x0001, 5, 10, "E") //
), list(it));
assertEquals(list(
ent(0x0000, 0x0fff, 5, 10, "W"),
ent(0x1000, 0x1fff, Long.MIN_VALUE, Long.MIN_VALUE + 10, "S"),
ent(0x2000, 0x2fff, Long.MAX_VALUE - 10, Long.MAX_VALUE, "N"),
ent(-0x1000, -0x0001, 5, 10, "E")),
list(it));
}
@Test
public void testOcclusion() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoFutureIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 90, 95), "A");
space.put(tasr(0x1800, 0x27ff, 89, 95), "B");

View file

@ -157,7 +157,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterableTest
new LanguageID("Toy:BE:64:default"));
obj = new MyObject(this);
factory = new DBCachedObjectStoreFactory(obj);
try (UndoableTransaction tid = UndoableTransaction.start(obj, "CreateTable", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "CreateTable")) {
space = new DBTraceAddressSnapRangePropertyMapSpace<>("Entries", factory,
obj.getReadWriteLock(), toy.getDefaultSpace(), MyEntry.class, MyEntry::new);
}
@ -188,7 +188,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterableTest
@Test
public void testOutOfWindow() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 5, 10), "A");
}
@ -208,7 +208,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterableTest
@Test
public void testSingleEntry() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 5, 10), "A");
}
@ -232,7 +232,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterableTest
@Test
public void testEntriesAtExtremes() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x0000, 0x0fff, 5, 10), "W");
space.put(tasr(-0x1000, -0x0001, 5, 10), "E");
space.put(tasr(0x1000, 0x1fff, Long.MIN_VALUE, Long.MIN_VALUE + 10), "S");
@ -251,7 +251,7 @@ public class DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterableTest
@Test
public void testOcclusion() {
DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterable<String> it;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create Entries")) {
space.put(tasr(0x1000, 0x1fff, 5, 10), "A");
space.put(tasr(0x1800, 0x27ff, 5, 11), "B");

View file

@ -80,7 +80,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
}
protected void loadSpaces() throws VersionException, IOException {
try (UndoableTransaction tid = UndoableTransaction.start(this, "Create Tables", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(this, "Create Tables")) {
this.space1 = new DBTraceAddressSnapRangePropertyMapSpace<>("Entries1", factory,
getReadWriteLock(), toy.getDefaultSpace(), MyEntry.class, MyEntry::new);
this.space2 = new DBTraceAddressSnapRangePropertyMapSpace<>("Entries2", factory,
@ -236,7 +236,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
@Test
public void testDeleteValue() {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
MyEntry entry1 = obj.space1.put(at(0x1000, 5), null);
MyEntry entry2 = obj.space2.put(at(0x1001, 5), null);
String value3 = obj.space3.put(at(0x1002, 5), "Test");
@ -268,7 +268,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
@Test
@Ignore("TODO")
public void testRemove() {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
obj.space1.put(at(0x1000, 5), null);
obj.space2.put(at(0x1000, 5), null);
assertEquals(1, obj.space1.size());
@ -308,7 +308,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
public void testCollections() {
MyEntry entry1;
MyEntry entry2;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
entry1 = obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1000), 5), null);
entry2 = obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1001), 6), null);
}
@ -329,7 +329,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
@Test
public void testReduce() {
MyEntry ent1;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
ent1 = obj.space1.put(at(0x1000, 5), null);
}
@ -342,7 +342,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
@Test
public void testFirsts() {
MyEntry entry1;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
entry1 = obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1000), 5), null);
}
@ -353,7 +353,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
@Test
public void testClear() {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
MyEntry entry1 =
obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1000), 5), null);
assertEquals(1, obj.space1.size());
@ -369,7 +369,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
public void testGetDataByKey() {
assertNull(obj.space1.getDataByKey(0));
MyEntry entry1;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
entry1 = obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1000), 5), null);
}
@ -381,7 +381,7 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
@Ignore("TODO")
public void testSaveAndLoad() throws IOException, CancelledException, VersionException {
MyEntry entry1;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
entry1 = obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1000), 5), null);
}
assertEquals(ent(0x1000, 5, entry1), obj.space1.firstEntry());
@ -400,12 +400,12 @@ public class DBTraceAddressSnapRangePropertyMapSpaceTest
@Ignore("Related to GP-479")
public void testUndoThenRedo() throws IOException {
MyEntry entry1;
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Create entries")) {
entry1 = obj.space1.put(new ImmutableTraceAddressSnapRange(addr(0x1000), 5), null);
}
assertEquals(ent(0x1000, 5, entry1), obj.space1.firstEntry());
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Clear", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(obj, "Clear")) {
obj.space1.clear();
}
assertNull(obj.space1.firstEntry());

View file

@ -34,49 +34,50 @@ import ghidra.util.database.UndoableTransaction;
public class DBTraceStaticMappingManagerTest extends AbstractGhidraHeadlessIntegrationTest {
ToyDBTraceBuilder b;
ToyDBTraceBuilder tb;
DBTraceStaticMappingManager staticMappingManager;
@Before
public void setUpStaticMappingManagerTest() throws IOException {
b = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default");
staticMappingManager = b.trace.getStaticMappingManager();
tb = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default");
staticMappingManager = tb.trace.getStaticMappingManager();
}
@After
public void tearDownStaticMappingManagerTest() {
b.close();
tb.close();
}
@Test
public void testAddAndGet() throws Exception {
try (UndoableTransaction tid = b.startTransaction()) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closed(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99), Range.closed(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
}
DBTraceStaticMapping found = staticMappingManager.findContaining(b.addr(0xdeadbeef), 2);
assertEquals(b.addr(0xdeadbeef), found.getMinTraceAddress());
DBTraceStaticMapping found = staticMappingManager.findContaining(tb.addr(0xdeadbeef), 2);
assertEquals(tb.addr(0xdeadbeef), found.getMinTraceAddress());
assertEquals(100, found.getLength());
assertEquals(2, found.getStartSnap());
assertEquals(5, found.getEndSnap());
assertEquals(new URL("ghidra://static"), found.getStaticProgramURL());
assertEquals("DEADBEEF", found.getStaticAddress());
assertEquals(found, staticMappingManager.findContaining(b.addr(0xdeadbeef + 99), 2));
assertEquals(found, staticMappingManager.findContaining(b.addr(0xdeadbeef + 99), 5));
assertEquals(found, staticMappingManager.findContaining(b.addr(0xdeadbeef), 5));
assertEquals(found, staticMappingManager.findContaining(tb.addr(0xdeadbeef + 99), 2));
assertEquals(found, staticMappingManager.findContaining(tb.addr(0xdeadbeef + 99), 5));
assertEquals(found, staticMappingManager.findContaining(tb.addr(0xdeadbeef), 5));
assertNull(staticMappingManager.findContaining(b.addr(0xdeadbeef - 1), 2));
assertNull(staticMappingManager.findContaining(b.addr(0xdeadbeef + 100), 2));
assertNull(staticMappingManager.findContaining(b.addr(0xdeadbeef), 1));
assertNull(staticMappingManager.findContaining(b.addr(0xdeadbeef), 6));
assertNull(staticMappingManager.findContaining(tb.addr(0xdeadbeef - 1), 2));
assertNull(staticMappingManager.findContaining(tb.addr(0xdeadbeef + 100), 2));
assertNull(staticMappingManager.findContaining(tb.addr(0xdeadbeef), 1));
assertNull(staticMappingManager.findContaining(tb.addr(0xdeadbeef), 6));
}
@Test
public void testAddAndEnumerate() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
}
@ -86,10 +87,11 @@ public class DBTraceStaticMappingManagerTest extends AbstractGhidraHeadlessInteg
@Test
public void testAddRemoveAndEnumerate() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99),
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(7L, 10L),
new URL("ghidra://static"), "DEADBEEF");
@ -105,10 +107,11 @@ public class DBTraceStaticMappingManagerTest extends AbstractGhidraHeadlessInteg
@Test
public void testOverlapCausesException() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
staticMappingManager.add(b.range(0xdeadbeef + 80, 0xdeadbeef + 179),
staticMappingManager.add(tb.range(0xdeadbeef + 80, 0xdeadbeef + 179),
Range.closedOpen(2L, 5L), new URL("ghidra://static"), "DEADBEEF");
fail();
}
@ -119,20 +122,22 @@ public class DBTraceStaticMappingManagerTest extends AbstractGhidraHeadlessInteg
@Test
public void testOverlapAgreeingAccepted() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
staticMappingManager.add(b.range(0xdeadbeef + 80, 0xdeadbeef + 179),
staticMappingManager.add(tb.range(0xdeadbeef + 80, 0xdeadbeef + 179),
Range.closedOpen(2L, 5L), new URL("ghidra://static"), "DEADBF3F");
}
}
@Test
public void testTouchingProceedingIsNotOverlapping() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
staticMappingManager.add(b.range(0xdeadbeef + 100, 0xdeadbeef + 199),
staticMappingManager.add(tb.range(0xdeadbeef + 100, 0xdeadbeef + 199),
Range.closedOpen(2L, 5L), new URL("ghidra://static"), "DEADBEEF");
}
}
@ -140,12 +145,12 @@ public class DBTraceStaticMappingManagerTest extends AbstractGhidraHeadlessInteg
@SuppressWarnings("hiding")
@Test
public void testSaveAndLoad() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closed(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99), Range.closed(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
}
File tmp = b.save();
File tmp = tb.save();
try (ToyDBTraceBuilder b = new ToyDBTraceBuilder(tmp)) {
DBTraceStaticMappingManager staticMappingManager = b.trace.getStaticMappingManager();
DBTraceStaticMapping found =
@ -161,8 +166,9 @@ public class DBTraceStaticMappingManagerTest extends AbstractGhidraHeadlessInteg
@Test
public void testAddButAbortedStillEmpty() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
tid.abort();
}
@ -172,29 +178,31 @@ public class DBTraceStaticMappingManagerTest extends AbstractGhidraHeadlessInteg
@Test
public void testAddThenUndo() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
}
b.trace.undo();
tb.trace.undo();
assertEquals(0, staticMappingManager.getAllEntries().size());
}
@Test
public void testAddThenRemoveThenUndo() throws Exception {
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
staticMappingManager.add(b.range(0xdeadbeef, 0xdeadbeef + 99), Range.closedOpen(2L, 5L),
try (UndoableTransaction tid = tb.startTransaction()) {
staticMappingManager.add(tb.range(0xdeadbeef, 0xdeadbeef + 99),
Range.closedOpen(2L, 5L),
new URL("ghidra://static"), "DEADBEEF");
}
assertEquals(1, staticMappingManager.getAllEntries().size());
try (UndoableTransaction tid = UndoableTransaction.start(b.trace, "Testing", true)) {
try (UndoableTransaction tid = tb.startTransaction()) {
for (TraceStaticMapping m : staticMappingManager.getAllEntries()) {
m.delete();
}
}
assertEquals(0, staticMappingManager.getAllEntries().size());
b.trace.undo();
tb.trace.undo();
assertEquals(1, staticMappingManager.getAllEntries().size());
}
}

View file

@ -142,7 +142,7 @@ public class DBTraceDisassemblerIntegrationTest extends AbstractGhidraHeadlessIn
public void testThumbSampleProgramDB() throws Exception {
ProgramBuilder b = new ProgramBuilder(getName(), ProgramBuilder._ARM);
try (UndoableTransaction tid =
UndoableTransaction.start(b.getProgram(), "Disassemble (THUMB)", true)) {
UndoableTransaction.start(b.getProgram(), "Disassemble (THUMB)")) {
MemoryBlock text = b.createMemory(".text", "b6fa2cd0", 32, "Sample", (byte) 0);
text.putBytes(b.addr(0xb6fa2cdc), new byte[] {
// GDB: stmdb sp!, {r4,r5,r6,r7,r8,lr}

View file

@ -83,7 +83,7 @@ class DefaultVar implements LValInternal, Var {
check.check(ctx.parser, name);
this.ctx = ctx;
this.name = name;
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type")) {
this.type = ctx.dtm.resolve(type, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}

View file

@ -26,7 +26,7 @@ public abstract class Expr implements RValInternal {
protected Expr(StructuredSleigh ctx, DataType type) {
this.ctx = ctx;
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type", true)) {
try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type")) {
this.type = ctx.dtm.resolve(type, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}

View file

@ -19,9 +19,21 @@ import java.io.IOException;
import db.DBLongIterator;
/**
* An abstract implementation of {@link DirectedLongKeyIterator}
*
* <p>
* Essentially, this just wraps a {@link DBLongIterator}, but imposes and encapsulates its
* direction.
*/
public abstract class AbstractDirectedLongKeyIterator implements DirectedLongKeyIterator {
protected final DBLongIterator it;
/**
* Wrap the given iterator
*
* @param it the iterator
*/
public AbstractDirectedLongKeyIterator(DBLongIterator it) {
this.it = it;
}

View file

@ -19,9 +19,21 @@ import java.io.IOException;
import db.RecordIterator;
/**
* An abstract implementation of {@link DirectedRecordIterator}
*
* <p>
* Essentially, this just wraps a {@link RecordIterator}, but imposes and encapsulates its
* direction.
*/
public abstract class AbstractDirectedRecordIterator implements DirectedRecordIterator {
protected final RecordIterator it;
/**
* Wrap the given iterator
*
* @param it the iterator
*/
public AbstractDirectedRecordIterator(RecordIterator it) {
this.it = it;
}

View file

@ -19,6 +19,10 @@ import java.io.IOException;
import db.DBLongIterator;
/**
* A wrapper of {@link DBLongIterator} that runs it backward and implements
* {@link DirectedLongKeyIterator}
*/
public class BackwardLongKeyIterator extends AbstractDirectedLongKeyIterator {
public BackwardLongKeyIterator(DBLongIterator it) {
super(it);

View file

@ -20,6 +20,10 @@ import java.io.IOException;
import db.DBRecord;
import db.RecordIterator;
/**
* A wrapper of {@link RecordIterator} that runs it backward and implements
* {@link DirectedRecordIterator}
*/
public class BackwardRecordIterator extends AbstractDirectedRecordIterator {
public BackwardRecordIterator(RecordIterator it) {
super(it);

View file

@ -22,16 +22,115 @@ import db.DBRecord;
import ghidra.program.database.DatabaseObject;
import ghidra.util.LockHold;
import ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec;
import ghidra.util.database.annot.DBAnnotatedObjectInfo;
/**
* An object backed by a {@link DBRecord}
*
* <p>
* Essentially, this is a data access object (DAO) for Ghidra's custom database engine. Not all
* object fields necessarily have a corresponding database field. Instead, those fields are
* annotated, and various methods are provided for updating the record, and conversely, re-loading
* fields from the record. These objects are managed using a {@link DBCachedObjectStore}. An example
* object definition:
*
* <pre>
* interface Person {
* // ...
* }
*
* &#64;DBAnnotatedObjectInfo(version = 1)
* public class DBPerson extends DBAnnotatedObject implements Person {
* public static final String TABLE_NAME = "Person"; // Conventionally defined here
*
* // Best practice is to define column names, then use in annotations
* static final String NAME_COLUMN_NAME = "Name";
* static final String ADDRESS_COLUMN_NAME = "Address";
*
* // Column handles
* &#64;DBAnnotatedColumn(NAME_COLUMN_NAME)
* static DBObjectColumn NAME_COLUMN;
* &#64;DBAnnotatedColumn(ADDRESS_COLUMN_NAME)
* static DBObjectColumn ADDRESS_COLUMN;
*
* // Column-backed fields
* &#64;DBAnnotatedField(column = NAME_COLUMN_NAME, indexed = true)
* private String name;
* &#64;DBAnnotatedField(column = ADDRESS_COLUMN_NAME)
* private String address;
*
* DBPerson(DBCachedObjectStore<DBPerson> store, DBRecord record) {
* super(store, record);
* }
*
* // Not required, but best practice
* private void set(String name, String address) {
* this.name = name;
* this.address = address;
* update(NAME_COLUMN, ADDRESS_COLUMN);
* }
*
* // ... other methods, getters, setters
* }
* </pre>
*
* <p>
* See {@link DBCachedObjectStoreFactory} for example code that uses the example {@code DBPerson}
* class.
*
* <p>
* All realizations of {@link DBAnnotatedObject} must be annotated with
* {@link DBAnnotatedObjectInfo}. This, along with the field annotations, are used to derive the
* table schema. Note the inclusion of a {@code TABLE_NAME} field. It is not required, nor is it
* used implicitly. It's included in this example as a manner of demonstrating best practice. When
* instantiating the object store, the field is used to provide the table name.
* <p>
* Next, we define the column names. These are not required nor used implicitly, but using literal
* strings in the column annotations is discouraged. Next, we declare variables to receive column
* handles. These are essentially the column numbers, but we have a named handle for each. They are
* initialized automatically the first time a store is created for this class.
* <p>
* Next we declare the variables representing the actual column values. Their initialization varies
* depending on how the object is instantiated. When creating a new object, the fields remain
* uninitialized. In some cases, it may be appropriate to provide an initial (default) value in the
* usual fashion, e.g., {@code private String address = "123 Pine St.";} In this case, the
* corresponding database field of the backing record is implicitly initialized upon creation. If
* the object is being loaded from a table, its fields are initialized with values from its backing
* record.
*
* <p>
* Next we define the constructor. There are no requirements on its signature, but it must call
* {@link #DBAnnotatedObject(DBCachedObjectStore, DBRecord) super}, so it likely takes its
* containing store and its backing record. Having the same signature as its super constructor
* allows the store to be created using a simple method reference, e.g., {@code DBPerson::new}.
* Additional user-defined parameters may be accepted. To pass such parameters, a lambda is
* recommended when creating the object store.
* <p>
* Finally, we demonstrate how to update the record. The record is <em>not</em> implicitly updated
* by direct modification of an annotated field. All setters must call
* {@link #update(DBObjectColumn...)} after updating a field. A common practice, especially when the
* object will have all its fields set at once, is to include a {@code set} method that initializes
* the fields and updates the record in one {@link #update(DBObjectColumn...)}.
*
* <p>
* Note that there is no way to specify the primary key. For object stores, the primary key is
* always the object id, and its type is always {@code long}.
*/
public class DBAnnotatedObject extends DatabaseObject {
private final DBCachedObjectStore<?> store;
private final DBCachedDomainObjectAdapter adapter;
private final List<DBFieldCodec<?, ?, ?>> codecs;
private final List<DBFieldCodec<?, ?, ?>> codecs; // The codecs, ordered by field
DBRecord record;
DBRecord record; // The backing record
/**
* The object constructor
*
* @param store the store containing this object
* @param record the record backing this object
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public DBAnnotatedObject(DBCachedObjectStore<?> store, DBRecord record) {
protected DBAnnotatedObject(DBCachedObjectStore<?> store, DBRecord record) {
super(store == null ? null : store.cache, record == null ? -1 : record.getKey());
this.store = store;
this.record = record;
@ -51,54 +150,77 @@ public class DBAnnotatedObject extends DatabaseObject {
* @return the opaque object id
*/
public ObjectKey getObjectKey() {
return new ObjectKey(store.adapter, store.table.getName(), key);
return new ObjectKey(store.table, key);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void write(DBObjectColumn column) {
protected void doWrite(DBObjectColumn column) {
DBFieldCodec codec = codecs.get(column.columnNumber);
codec.store(this, record);
}
/**
* 1-arity version of {@link #update(DBObjectColumn...)}
*
* @param column the column
*/
protected void update(DBObjectColumn column) {
write(column);
doWrite(column);
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
}
}
/**
* 2-arity version of {@link #update(DBObjectColumn...)}
*
* @param col1 a column
* @param col2 another column
*/
protected void update(DBObjectColumn col1, DBObjectColumn col2) {
write(col1);
write(col2);
doWrite(col1);
doWrite(col2);
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
}
}
/**
* 2-arity version of {@link #update(DBObjectColumn...)}
*
* @param col1 a column
* @param col2 another column
* @param col3 another column
*/
protected void update(DBObjectColumn col1, DBObjectColumn col2, DBObjectColumn col3) {
write(col1);
write(col2);
write(col3);
doWrite(col1);
doWrite(col2);
doWrite(col3);
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
}
}
/**
* Write the given columns into the record and update the table
*
* @param columns the columns to update
*/
protected void update(DBObjectColumn... columns) {
for (DBObjectColumn c : columns) {
write(c);
doWrite(c);
}
try (LockHold hold = LockHold.lock(store.writeLock())) {
updated();
doUpdated();
}
catch (IOException e) {
store.dbError(e);
@ -110,28 +232,36 @@ public class DBAnnotatedObject extends DatabaseObject {
for (DBFieldCodec codec : codecs) {
codec.store(this, record);
}
updated();
doUpdated();
}
protected void updated() throws IOException {
protected void doUpdated() throws IOException {
store.table.putRecord(record);
}
/**
* Called when the object's fields are populated.
* Extension point: Called when the object's fields are populated.
*
* This provides an opportunity for the object to initialize any remaining (usually
* non-database-backed) fields.
* <p>
* This provides an opportunity for the object to initialize any non-database-backed fields that
* depend on the database-backed fields. Note that its use may indicate a situation better
* solved by a custom {@link DBFieldCodec}. If both the database-backed and non-database-backed
* fields are used frequently, then a codec may not be indicated. If the database-backed fields
* are only used in this method or to encode another frequently-used field, then a codec is
* likely better.
*
* <p>
* For a new object, the database-backed fields remain at their initial values. They will be
* saved after this method returns, so they may be further initialized with custom logic.
*
* For an object loaded from the database, the database-backed fields were already populated
* from the record. They are <em>not</em> automatically saved after this method returns. This
* method should not further initialize database-backed fields in this case.
* <p>
* For an object loaded from the database, the database-backed fields are already populated from
* the record when this method is called. They are <em>not</em> automatically saved after this
* method returns. This method should not further initialize database-backed fields in this
* case.
*
* @param created {@code true} to indicate the object is being created, or {@code false} to
* indicate it is being restored.
* @param created {@code true} when object is being created, or {@code false} when it is being
* loaded.
* @throws IOException if further initialization fails.
*/
protected void fresh(boolean created) throws IOException {
@ -187,6 +317,12 @@ public class DBAnnotatedObject extends DatabaseObject {
return true;
}
/**
* Check if this object has been deleted
*
* @see #isDeleted(ghidra.util.Lock)
* @return true if deleted
*/
public boolean isDeleted() {
return super.isDeleted(adapter.getLock());
}

View file

@ -17,6 +17,11 @@ package ghidra.util.database;
import db.DBRecord;
/**
* Needed by a {@link DBCachedObjectStore} to describe how to construct the objects it manages
*
* @param <T> the type of objects in the store
*/
public interface DBAnnotatedObjectFactory<T extends DBAnnotatedObject> {
T create(DBCachedObjectStore<T> store, DBRecord record);
}

View file

@ -24,6 +24,15 @@ import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
/**
* A domain object that can use {@link DBCachedObjectStoreFactory}.
*
* <p>
* Technically, this only introduces a read-write lock to the domain object. The
* {@link DBCachedObjectStoreFactory} and related require this read-write lock. Sadly, this idea
* didn't pan out, and that read-write lock is just a degenerate wrapper of the Ghidra
* {@link ghidra.util.Lock}, which is not a read-write lock. This class may disappear.
*/
public abstract class DBCachedDomainObjectAdapter extends DBDomainObjectSupport {
static class SwingAwareReadWriteLock extends ReentrantReadWriteLock {
@ -151,12 +160,20 @@ public abstract class DBCachedDomainObjectAdapter extends DBDomainObjectSupport
protected ReadWriteLock rwLock;
/**
* @see {@link DBDomainObjectSupport}
*/
protected DBCachedDomainObjectAdapter(DBHandle dbh, DBOpenMode openMode, TaskMonitor monitor,
String name, int timeInterval, int bufSize, Object consumer) {
super(dbh, openMode, monitor, name, timeInterval, bufSize, consumer);
this.rwLock = new GhidraLockWrappingRWLock(lock);
}
/**
* Get the "read-write" lock
*
* @return the lock
*/
public ReadWriteLock getReadWriteLock() {
return rwLock;
}

View file

@ -27,12 +27,20 @@ import ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec;
import ghidra.util.database.DirectedIterator.Direction;
/**
* An index on a field in a {@link DBCachedObjectStore}
*
* NOTE: This seems ripe for implementing a collection interface, but each defies implementation on
* our DB framework.
* <p>
* This provides access to a table index backing the store, allowing clients to retrieve objects
* having specified field values. Its methods are inspired by {@link NavigableMap}; however, its
* semantics permit duplicate keys, so this cannot implement it in the manner desired.
*
* @param <K>
* @param <T>
* @implNote This seems rife for implementing a collection interface, but each defies implementation
* on our DB framework. Probably because it's better understood as a multimap, which is
* not a standard Java collection. Guava's proved too burdensome to implement. We never
* tried Apache's.
*
* @param <K> the type of keys in the index, i.e., the indexed field's type
* @param <T> the type of objects in the store
*/
public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
protected final DBCachedObjectStore<T> store;
@ -43,6 +51,19 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
protected final Range<Field> fieldRange;
protected final Direction direction;
/**
* Construct an index
*
* <p>
* Clients should use {@link DBCachedObjectStore#getIndex(Class, DBObjectColumn)}.
*
* @param store the store containing the indexed objects
* @param errHandler an error handler
* @param codec the codec for the indexed field/column
* @param columnIndex the column number
* @param fieldRange required: the restricted range, can be {@link Range#all()}
* @param direction the sort order / direction of iteration
*/
protected DBCachedObjectIndex(DBCachedObjectStore<T> store, ErrorHandler errHandler,
DBFieldCodec<K, T, ?> codec, int columnIndex, Range<Field> fieldRange,
Direction direction) {
@ -54,45 +75,83 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
this.direction = direction;
}
public DBCachedObjectStoreFoundKeysValueCollection<T> get(K key) {
Field field = codec.encodeField(key);
if (!fieldRange.contains(field)) {
return null;
}
protected Collection<T> get(Field encoded) {
try {
return store.findObjects(columnIndex, field);
return store.findObjects(columnIndex, encoded);
}
catch (IOException e) {
errHandler.dbError(e);
return null;
return List.of();
}
}
/**
* Get the objects having the given value in the indexed field
*
* NOTE: Not sensitive to bounds at all
* <p>
* <b>NOTE:</b> The objects' primary keys are retrieved immediately, but the returned collection
* loads each requested object lazily. This may have timing implications. If the returned
* collection is used at a later time, the keys found may no longer be valid, and even if they
* are, the indexed field may no longer have the requested value when retrieved. See
* {@link #getLazily(Object)}.
*
* @param key
* @return
* @param key the value
* @return the collection of objects
*/
public Collection<T> get(K key) {
Field encoded = codec.encodeField(key);
if (!fieldRange.contains(encoded)) {
return List.of();
}
return get(encoded);
}
/**
* Get the objects having the given value in the index field
*
* <p>
* This differs from {@link #get(Object)} in that the keys are retrieved each time the
* collection is iterated. The returned collection can be saved and used later. The iterator
* itself still has a fixed set of keys, though, so clients should use it and discard it in a
* timely fashion, and/or while holding the domain object's lock.
*
* @param key the value
* @return the lazy collection of objects
*/
public Collection<T> getLazily(K key) {
Field encoded = codec.encodeField(key);
if (!fieldRange.contains(encoded)) {
return List.of();
}
return new AbstractCollection<>() {
@Override
public Iterator<T> iterator() {
return get(key).iterator();
return get(encoded).iterator();
}
@Override
public int size() {
return countKey(key);
return countKey(encoded);
}
public boolean isEmpty() {
return !containsKey(key);
return !containsKey(encoded);
}
};
}
/**
* Get a unique object having the given value in the index field
*
* <p>
* Clients should use this method when the index behaves like a map, rather than a multimap. It
* is the client's responsibility to ensure that duplicate values do not exist in the indexed
* column.
*
* @param value the value
* @return the object, if found, or null
* @throws IllegalStateException if the object is not unique
*/
public T getOne(K value) {
Field field = codec.encodeField(value);
if (!fieldRange.contains(field)) {
@ -107,6 +166,14 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
}
}
/**
* Iterate over the values of the indexed column, in order
*
* <p>
* Despite being called keys, the values may not be unique
*
* @return the iterator
*/
public Iterable<K> keys() {
return new Iterable<>() {
@Override
@ -134,6 +201,11 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
};
}
/**
* Iterate over the objects as ordered by the index
*
* @return the iterator
*/
public Iterable<T> values() {
return new Iterable<>() {
@Override
@ -149,6 +221,15 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
};
}
/**
* Iterate over the entries as ordered by the index
*
* <p>
* Each entry is a key-value value where the "key" is the value of the indexed field, and the
* "value" is the object.
*
* @return the iterator
*/
public Iterable<Entry<K, T>> entries() {
return new Iterable<>() {
@Override
@ -181,17 +262,23 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
return it.hasNext() ? it.next() : null;
}
/**
* Check if this index is empty
*
* <p>
* Except for sub-ranged indexes, this is equivalent to checking if the object store is empty.
* For sub-ranged indexes, this checks if the store contains any object whose value for the
* indexed field falls within the restricted range.
*
* @return true if empty
*/
public boolean isEmpty() {
return values().iterator().hasNext();
}
public boolean containsKey(K key) {
Field field = codec.encodeField(key);
if (!fieldRange.contains(field)) {
return false;
}
protected boolean containsKey(Field encoded) {
try {
return store.table.hasRecord(field, columnIndex);
return store.table.hasRecord(encoded, columnIndex);
}
catch (IOException e) {
store.dbError(e);
@ -199,6 +286,33 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
}
}
/**
* Check if there is any object having the given value for its indexed field
*
* <p>
* This method is more efficient than using {@code get(key).isEmpty()}, since it need only find
* one match, whereas {@link #get(Object)} will retrieve every match. Granted, it doesn't make
* sense to immediately call {@link #get(Object)} after {@link #containsKey(Object)} returns
* true.
*/
public boolean containsKey(K key) {
Field encoded = codec.encodeField(key);
if (!fieldRange.contains(encoded)) {
return false;
}
return containsKey(encoded);
}
/**
* Check if the given object is in the index
*
* <p>
* Except for sub-ranged indexes, this is equivalent to checking if the object is in the store.
* For a sub-ranged index, the value of its indexed field must fall within the restricted range.
*
* @param value the object
* @return true if it appears in this (sub-ranged) index.
*/
public boolean containsValue(T value) {
if (!fieldRange.contains(value.record.getFieldValue(columnIndex))) {
return false;
@ -206,13 +320,9 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
return store.contains(value);
}
public int countKey(K key) {
Field field = codec.encodeField(key);
if (!fieldRange.contains(field)) {
return 0;
}
protected int countKey(Field encoded) {
try {
return store.table.getMatchingRecordCount(field, columnIndex);
return store.table.getMatchingRecordCount(encoded, columnIndex);
}
catch (IOException e) {
store.dbError(e);
@ -220,78 +330,239 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
}
}
/**
* Count the number of objects whose indexed field has the given value
*
* @param key the value
* @return the count
*/
public int countKey(K key) {
Field encoded = codec.encodeField(key);
if (!fieldRange.contains(encoded)) {
return 0;
}
return countKey(encoded);
}
/**
* Get the first key in the index
*
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the first key, or null
*/
public K firstKey() {
return firstOf(keys());
}
/**
* Get the first object in the index
*
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the first object, or null
*/
public T firstValue() {
return firstOf(values());
}
/**
* Get the first entry in the index
*
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the first key, or null
*/
public Entry<K, T> firstEntry() {
return firstOf(entries());
}
/**
* Get the last key in the index
*
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the first key, or null
*/
public K lastKey() {
return firstOf(descending().keys());
}
/**
* Get the last object in the index
*
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the first object, or null
*/
public T lastValue() {
return firstOf(descending().values());
}
/**
* Get the last entry in the index
*
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the first key, or null
*/
public Entry<K, T> lastEntry() {
return firstOf(descending().entries());
}
/**
* Get the key before the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the previous key, or null
*/
public K lowerKey(K key) {
return firstOf(head(key, false).descending().keys());
}
/**
* Get the value before the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the the value of the previous key, or null
*/
public T lowerValue(K key) {
return firstOf(head(key, false).descending().values());
}
/**
* Get the entry before the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the entry of the previous key, or null
*/
public Entry<K, T> lowerEntry(K key) {
return firstOf(head(key, false).descending().entries());
}
/**
* Get the key at or before the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the same or previous key, or null
*/
public K floorKey(K key) {
return firstOf(head(key, true).descending().keys());
}
/**
* Get the value at or before the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the value of the same or previous key, or null
*/
public T floorValue(K key) {
return firstOf(head(key, true).descending().values());
}
/**
* Get the entry at or before the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the entry of the same or previous key, or null
*/
public Entry<K, T> floorEntry(K key) {
return firstOf(head(key, true).descending().entries());
}
/**
* Get the key at or after the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the same or next key, or null
*/
public K ceilingKey(K key) {
return firstOf(tail(key, true).keys());
}
/**
* Get the value at or after the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the value of the same or next key, or null
*/
public T ceilingValue(K key) {
return firstOf(tail(key, true).values());
}
/**
* Get the entry at or after the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the entry of the same or next key, or null
*/
public Entry<K, T> ceilingEntry(K key) {
return firstOf(tail(key, true).entries());
}
/**
* Get the key after the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the same or next key, or null
*/
public K higherKey(K key) {
return firstOf(tail(key, false).keys());
}
/**
* Get the value after the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the value of the next key, or null
*/
public T higherValue(K key) {
return firstOf(tail(key, false).values());
}
/**
* Get the entry after the given key
*
* @param key the key
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the entry of the next key, or null
*/
public Entry<K, T> higherEntry(K key) {
return firstOf(tail(key, false).entries());
}
/**
* Get a sub-ranged view of this index, limited to entries whose keys occur before the given key
*
* @param to the upper bound
* @param toInclusive whether the upper bound is included in the restricted view
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the restricted view
*/
public DBCachedObjectIndex<K, T> head(K to, boolean toInclusive) {
Range<Field> rng =
DBCachedObjectStore.toRangeHead(codec.encodeField(to), toInclusive, direction);
@ -299,6 +570,15 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
fieldRange.intersection(rng), direction);
}
/**
* Get a sub-ranged view of this index, limited to entries whose keys occur after the given key
*
* @param from the lower bound
* @param fromInclusive whether the lower bound is included in the restricted view
* @see #descending()
* @see #sub(Object, boolean, Object, boolean)
* @return the restricted view
*/
public DBCachedObjectIndex<K, T> tail(K from, boolean fromInclusive) {
Range<Field> rng =
DBCachedObjectStore.toRangeTail(codec.encodeField(from), fromInclusive, direction);
@ -306,6 +586,16 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
fieldRange.intersection(rng), direction);
}
/**
* Get a sub-ranged view of this index
*
* @param from the lower bound
* @param fromInclusive whether the lower bound is included in the restricted view
* @param to the upper bound
* @param toInclusive whether the upper bound is included in the restricted view
* @see #descending()
* @return the restricted view
*/
public DBCachedObjectIndex<K, T> sub(K from, boolean fromInclusive, K to, boolean toInclusive) {
Range<Field> rng = DBCachedObjectStore.toRange(codec.encodeField(from), fromInclusive,
codec.encodeField(to), toInclusive, direction);
@ -313,6 +603,18 @@ public class DBCachedObjectIndex<K, T extends DBAnnotatedObject> {
fieldRange.intersection(rng), direction);
}
/**
* Get a reversed view of this index
*
* <p>
* This affects iteration as well as all the navigation and sub-ranging methods. E.g.,
* {@link #lowerKey(Object)} in the reversed view will behave like {@link #higherKey(Object)} in
* the original. In other words, the returned index is equivalent to the original, but with a
* negated comparator. Calling {@link #descending()} on the returned view will return a view
* equivalent to the original.
*
* @return the reversed view
*/
public DBCachedObjectIndex<K, T> descending() {
return new DBCachedObjectIndex<>(store, errHandler, codec, columnIndex, fieldRange,
Direction.reverse(direction));

View file

@ -20,6 +20,7 @@ import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.function.Supplier;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@ -35,7 +36,28 @@ import ghidra.program.model.address.KeyRange;
import ghidra.util.LockHold;
import ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec;
import ghidra.util.database.DirectedIterator.Direction;
import ghidra.util.database.annot.DBAnnotatedField;
/**
* An object store backed by a {@link db.Table}
*
* <p>
* Essentially, this provides object-based accessed to records in the table via DAOs. See
* {@link DBAnnotatedObject} for further documentation including an example object definition. The
* store keeps a cache of objects using {@link DBObjectCache}. See
* {@link DBCachedObjectStoreFactory} for documentation describing how to create a store, including
* for the example object definition.
*
* <p>
* The store provides views for locating, iterating, and retrieving its objects in a variety of
* fashions. This includes the primary key (object id), or any indexed column (see
* {@link DBAnnotatedField#indexed()}). These views generally implement an interface from Java's
* Collections API, providing for familiar semantics. A notable exception is that none of the
* interfaces support mutation, aside from deletion. The store is populated only via the
* {@link #create()} methods.
*
* @param <T> the type of objects stored
*/
public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHandler {
private static final Comparator<? super Long> KEY_COMPARATOR = Long::compare;
@ -65,14 +87,119 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return Range.upTo(from, fromInclusive ? BoundType.CLOSED : BoundType.OPEN);
}
protected abstract class BoundedStuff<U, V> {
abstract U fromRecord(DBRecord record) throws IOException;
/**
* Abstractions for navigating within a given view
*
* <p>
* Generally, these are all methods that facilitate implementation of a {@link Collection} or
* {@link NavigableMap}. The idea is that the abstract methods are required to translate from
* various object types and to facilitate table access. This class then provides all the methods
* needed to navigate the table with respect to a desired element type. These types will be
* those typically exposed as collections by the {@link Map} interface: keys, values, and
* entries. The implementations of those collections can then call those methods as needed.
*
* <p>
* The methods are implemented in various groups and with a variety of parameters. The first
* group is the abstract methods. The next simply wraps the table's navigations methods to
* retrieve elements of the view. Many of these accept an optional range to limit the search or
* effect. This is to facilitate the implementation of sub-maps. The next are named after their
* counterparts in the navigable interfaces. In addition to the optional range, many of these
* take a direction. This is to facilitate the implementation of reversed collections. To best
* understand the methods, examine the callers-to tree and see the relevant documentation,
* probably in the Java Collections API.
*
* @param <E> the type of elements exposed by the view
* @param <R> the type used to navigate the view's backing
*/
protected abstract class BoundedStuff<E, R> {
/**
* Get the element from a given record
*
* @param record the table record
* @return the element
* @throws IOException if there's an issue reading the record
*/
abstract E fromRecord(DBRecord record) throws IOException;
abstract U fromObject(T value);
/**
* Get the element from a given store object
*
* @param value the store object
* @return the element
*/
abstract E fromObject(T value);
abstract Long getKey(U of);
/**
* Get the key of the record backing the given element
*
* @param of the element
* @return the key
*/
abstract Long getKey(E of);
U getMax() throws IOException {
/**
* Check that the object is the expected element type and return it or null
*
* <p>
* This is needed to implement {@link Collection#contains(Object)} and similar, because its
* signature accepts any object. The first step is to type check it. Note that if {@link E}
* is parameterized, it's fields may also require type checking.
*
* @param o the object whose type to check
* @return the object if its type matches an element, or null
*/
abstract E checkAndConvert(Object o);
/**
* Check if the given element is contained in the view
*
* @param e the element
* @return true if contained in the view
* @throws IOException if there's an issue reading the table
*/
abstract boolean typedContains(E e) throws IOException;
/**
* Remove the given element from the view
*
* @param e the element
* @return the store object removed or null if no effect
* @throws IOException if there's an issue accessing the table
*/
abstract T typedRemove(E e) throws IOException;
/**
* Get an iterator over the raw components of the table for the given range
*
* @param direction the direction of iteration
* @param keyRange the range of keys
* @return the iterator
* @throws IOException if there's an issue reading the table
*/
abstract DirectedIterator<R> rawIterator(Direction direction, Range<Long> keyRange)
throws IOException;
/**
* Convert the raw component to an element
*
* @param raw the raw component
* @return the element
* @throws IOException if there's an issue reading the table
*/
abstract E fromRaw(R raw) throws IOException;
// Utilities
E filter(E candidate, Range<Long> keyRange) {
if (candidate == null || !keyRange.contains(getKey(candidate))) {
return null;
}
return candidate;
}
// Methods which wrap the table's navigation methods
E getMax() throws IOException {
long max = table.getMaxKey();
if (max == Long.MIN_VALUE) {
return null;
@ -80,11 +207,11 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return get(max);
}
U getBefore(long key) throws IOException {
E getBefore(long key) throws IOException {
return fromRecord(table.getRecordBefore(key));
}
U getBefore(long key, Range<Long> keyRange) throws IOException {
E getBefore(long key, Range<Long> keyRange) throws IOException {
if (!keyRange.hasUpperBound() || key <= keyRange.upperEndpoint()) {
return filter(getBefore(key), keyRange);
}
@ -96,11 +223,11 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
U getAtOrBefore(long key) throws IOException {
E getAtOrBefore(long key) throws IOException {
return fromRecord(table.getRecordAtOrBefore(key));
}
U getAtOrBefore(long key, Range<Long> keyRange) throws IOException {
E getAtOrBefore(long key, Range<Long> keyRange) throws IOException {
if (!keyRange.hasUpperBound() || key < keyRange.upperEndpoint()) {
return filter(getAtOrBefore(key), keyRange);
}
@ -112,7 +239,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
U get(long key) throws IOException {
E get(long key) throws IOException {
T cached = cache.get(key);
if (cached != null) {
return fromObject(cached);
@ -120,11 +247,11 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return fromRecord(table.getRecord(key));
}
U getAtOrAfter(long key) throws IOException {
E getAtOrAfter(long key) throws IOException {
return fromRecord(table.getRecordAtOrAfter(key));
}
U getAtOrAfter(long key, Range<Long> keyRange) throws IOException {
E getAtOrAfter(long key, Range<Long> keyRange) throws IOException {
if (!keyRange.hasLowerBound() || key > keyRange.lowerEndpoint()) {
return filter(getAtOrAfter(key), keyRange);
}
@ -136,11 +263,11 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
U getAfter(long key) throws IOException {
E getAfter(long key) throws IOException {
return fromRecord(table.getRecordAfter(key));
}
U getAfter(long key, Range<Long> keyRange) throws IOException {
E getAfter(long key, Range<Long> keyRange) throws IOException {
if (!keyRange.hasLowerBound() || key >= keyRange.lowerEndpoint()) {
return filter(getAfter(key), keyRange);
}
@ -152,12 +279,8 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
abstract U checkAndConvert(Object o);
abstract boolean typedContains(U u) throws IOException;
boolean contains(Object o) throws IOException {
U u = checkAndConvert(o);
E u = checkAndConvert(o);
if (u == null) {
return false;
}
@ -165,7 +288,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
boolean contains(Object o, Range<Long> keyRange) throws IOException {
U u = checkAndConvert(o);
E u = checkAndConvert(o);
if (u == null) {
return false;
}
@ -193,10 +316,8 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return true;
}
abstract T typedRemove(U u) throws IOException;
boolean remove(Object o) throws IOException {
U u = checkAndConvert(o);
E u = checkAndConvert(o);
if (u == null) {
return false;
}
@ -204,7 +325,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
boolean remove(Object o, Range<Long> keyRange) throws IOException {
U u = checkAndConvert(o);
E u = checkAndConvert(o);
if (u == null) {
return false;
}
@ -230,18 +351,13 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return result;
}
U filter(U candidate, Range<Long> keyRange) {
if (candidate == null || !keyRange.contains(getKey(candidate))) {
return null;
}
return candidate;
}
// Methods for implementing navigable maps and collections
U first() throws IOException {
E first() throws IOException {
return getAtOrAfter(Long.MIN_VALUE);
}
U first(Range<Long> keyRange) throws IOException {
E first(Range<Long> keyRange) throws IOException {
if (!keyRange.hasLowerBound()) {
return filter(first(), keyRange);
}
@ -253,25 +369,25 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
U first(Direction direction) throws IOException {
E first(Direction direction) throws IOException {
if (direction == Direction.FORWARD) {
return first();
}
return last();
}
U first(Direction direction, Range<Long> keyRange) throws IOException {
E first(Direction direction, Range<Long> keyRange) throws IOException {
if (direction == Direction.FORWARD) {
return first(keyRange);
}
return last(keyRange);
}
U last() throws IOException {
E last() throws IOException {
return getMax();
}
U last(Range<Long> keyRange) throws IOException {
E last(Range<Long> keyRange) throws IOException {
if (!keyRange.hasUpperBound()) {
return filter(last(), keyRange);
}
@ -283,82 +399,77 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
U last(Direction direction) throws IOException {
E last(Direction direction) throws IOException {
if (direction == Direction.FORWARD) {
return last();
}
return first();
}
U last(Direction direction, Range<Long> keyRange) throws IOException {
E last(Direction direction, Range<Long> keyRange) throws IOException {
if (direction == Direction.FORWARD) {
return last(keyRange);
}
return first(keyRange);
}
U lower(Direction direction, long key) throws IOException {
E lower(Direction direction, long key) throws IOException {
if (direction == Direction.FORWARD) {
return getBefore(key);
}
return getAfter(key);
}
U lower(Direction direction, long key, Range<Long> keyRange) throws IOException {
E lower(Direction direction, long key, Range<Long> keyRange) throws IOException {
if (direction == Direction.FORWARD) {
return getBefore(key, keyRange);
}
return getAfter(key, keyRange);
}
U floor(Direction direction, long key) throws IOException {
E floor(Direction direction, long key) throws IOException {
if (direction == Direction.FORWARD) {
return getAtOrBefore(key);
}
return getAtOrAfter(key);
}
U floor(Direction direction, long key, Range<Long> keyRange) throws IOException {
E floor(Direction direction, long key, Range<Long> keyRange) throws IOException {
if (direction == Direction.FORWARD) {
return getAtOrBefore(key, keyRange);
}
return getAtOrAfter(key, keyRange);
}
U ceiling(Direction direction, long key) throws IOException {
E ceiling(Direction direction, long key) throws IOException {
if (direction == Direction.FORWARD) {
return getAtOrAfter(key);
}
return getAtOrBefore(key);
}
U ceiling(Direction direction, long key, Range<Long> keyRange) throws IOException {
E ceiling(Direction direction, long key, Range<Long> keyRange) throws IOException {
if (direction == Direction.FORWARD) {
return getAtOrAfter(key, keyRange);
}
return getAtOrBefore(key, keyRange);
}
U higher(Direction direction, long key) throws IOException {
E higher(Direction direction, long key) throws IOException {
if (direction == Direction.FORWARD) {
return getAfter(key);
}
return getBefore(key);
}
U higher(Direction direction, long key, Range<Long> keyRange) throws IOException {
E higher(Direction direction, long key, Range<Long> keyRange) throws IOException {
if (direction == Direction.FORWARD) {
return getAfter(key, keyRange);
}
return getBefore(key, keyRange);
}
abstract DirectedIterator<V> rawIterator(Direction direction, Range<Long> keyRange)
throws IOException;
abstract U fromRaw(V raw) throws IOException;
Iterator<U> iterator(DirectedIterator<V> it) {
Iterator<E> iterator(DirectedIterator<R> it) {
return new Iterator<>() {
@Override
public boolean hasNext() {
@ -372,7 +483,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
@Override
public U next() {
public E next() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return fromRaw(it.next());
}
@ -394,7 +505,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
};
}
Iterator<U> iterator(Direction direction, Range<Long> keyRange) {
Iterator<E> iterator(Direction direction, Range<Long> keyRange) {
if (keyRange != null && keyRange.isEmpty()) {
return Collections.emptyIterator();
}
@ -407,12 +518,12 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
void intoArray(U[] arr, Direction direction, Range<Long> keyRange) {
void intoArray(E[] arr, Direction direction, Range<Long> keyRange) {
if (keyRange != null && keyRange.isEmpty()) {
return;
}
try (LockHold hold = LockHold.lock(lock.readLock())) {
DirectedIterator<V> it = rawIterator(direction, keyRange);
DirectedIterator<R> it = rawIterator(direction, keyRange);
for (int i = 0; it.hasNext(); i++) {
arr[i] = fromRaw(it.next());
}
@ -422,12 +533,12 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
void toList(List<? super U> list, Direction direction, Range<Long> keyRange) {
void toList(List<? super E> list, Direction direction, Range<Long> keyRange) {
if (keyRange != null && keyRange.isEmpty()) {
return;
}
try (LockHold hold = LockHold.lock(lock.readLock())) {
DirectedIterator<V> it = rawIterator(direction, keyRange);
DirectedIterator<R> it = rawIterator(direction, keyRange);
while (it.hasNext()) {
list.add(fromRaw(it.next()));
}
@ -438,7 +549,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
Object[] toArray(Direction direction, Range<Long> keyRange) {
ArrayList<U> list = new ArrayList<>();
ArrayList<E> list = new ArrayList<>();
toList(list, direction, keyRange);
return list.toArray();
}
@ -455,7 +566,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
toList(list, direction, keyRange);
return list.toArray(a);
}
intoArray((U[]) a, direction, keyRange);
intoArray((E[]) a, direction, keyRange);
for (int i = size; i < a.length; i++) {
a[i] = null;
}
@ -468,9 +579,9 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
boolean result = false;
try (LockHold hold = LockHold.lock(lock.writeLock())) {
DirectedIterator<V> it = rawIterator(Direction.FORWARD, keyRange);
DirectedIterator<R> it = rawIterator(Direction.FORWARD, keyRange);
while (it.hasNext()) {
U u = fromRaw(it.next());
E u = fromRaw(it.next());
if (!c.contains(u)) {
it.delete();
cache.delete(getKey(u));
@ -485,6 +596,14 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* The implementation of {@link BoundedStuff} to facilitate the implementation of
* {@link Map#keySet()}.
*
* <p>
* Because tables let us navigate keys directly, we use the key as the raw component here
* instead of the full record.
*/
protected final BoundedStuff<Long, Long> keys = new BoundedStuff<>() {
@Override
Long fromRecord(DBRecord record) {
@ -557,6 +676,10 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
};
/**
* The implementation of {@link BoundedStuff} to facilitate the implementation of
* {@link Map#values()}.
*/
protected final BoundedStuff<T, DBRecord> objects = new BoundedStuff<>() {
@Override
T fromRecord(DBRecord record) throws IOException {
@ -620,6 +743,10 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
};
/**
* The implementation of {@link BoundedStuff} to facilitate the implementation of
* {@link Map#entrySet()}.
*/
protected final BoundedStuff<Entry<Long, T>, DBRecord> entries = new BoundedStuff<>() {
@Override
Entry<Long, T> fromRecord(DBRecord record) throws IOException {
@ -702,7 +829,19 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
Table table;
public DBCachedObjectStore(DBCachedDomainObjectAdapter adapter, Class<T> objectType,
/**
* Construct a store
*
* <p>
* Users should instead construct stores using
* {@link DBCachedObjectStoreFactory#getOrCreateCachedStore(String, Class, DBAnnotatedObjectFactory, boolean)}.
*
* @param adapter the domain object backed by the same database as this store
* @param objectType the type of objects stored
* @param factory the factory creating this store
* @param table the table backing this store
*/
protected DBCachedObjectStore(DBCachedDomainObjectAdapter adapter, Class<T> objectType,
DBAnnotatedObjectFactory<T> factory, Table table) {
this.adapter = adapter;
this.dbh = adapter.getDBHandle();
@ -738,7 +877,8 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
/**
* Get the maximum key which has ever existed in this store
*
* Note, the key need not actually be present
* <p>
* Note, the returned key may not actually be present
*
* @return the maximum, or null if the store is unused
*/
@ -755,6 +895,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
/**
* Count the number of keys in a given range.
*
* <p>
* This implementation is not very efficient. It must visit at least every record in the range.
*
* @param keyRange the range of keys
@ -785,6 +926,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
/**
* Check if any keys exist within the given range.
*
* <p>
* This implementation is more efficient than using {@link #getKeyCount(Range)} and comparing to
* 0, since there's no need to visit more than one record in the range.
*
@ -820,6 +962,16 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* Check if an object with the given key exists in the store
*
* <p>
* Using this is preferred to {@link #getObjectAt(long)} and checking for null, if that object
* does not actually need to be retrieved.
*
* @param key the key
* @return true if it exists
*/
public boolean containsKey(long key) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return keys.typedContains(key);
@ -830,6 +982,16 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* Check if the given object exists in the store
*
* <p>
* No matter the definition of {@link T#equals(Object)}, this requires the identical object to
* be present.
*
* @param obj the object
* @return
*/
public boolean contains(T obj) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return objects.typedContains(obj);
@ -852,6 +1014,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
/**
* Create a new object with the given key.
*
* <p>
* If the key already exists in the table, the existing record is overwritten.
*
* @param key the key for the new object
@ -882,6 +1045,13 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* Get the column number given a column name
*
* @param name the name
* @return the number (0-up index) for the column
* @throws NoSuchElementException if no column with the given name exists
*/
protected int getColumnByName(String name) {
int index = ArrayUtils.indexOf(schema.getFieldNames(), name);
if (index < 0) {
@ -890,7 +1060,16 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return index;
}
protected <K> DBCachedObjectIndex<K, T> getIndex(Class<K> valueType, int columnIndex) {
/**
* Get the table index for the given column number
*
* @param <K> the type of the object field for the indexed column
* @param fieldClass the class specifying {@link K}
* @param columnIndex the column number
* @return the index
* @throws IllegalArgumentException if the column has a different type than {@link K}
*/
protected <K> DBCachedObjectIndex<K, T> getIndex(Class<K> fieldClass, int columnIndex) {
if (!ArrayUtils.contains(table.getIndexedColumns(), columnIndex)) {
throw new IllegalArgumentException(
"Column " + schema.getFieldNames()[columnIndex] + " is not indexed");
@ -898,9 +1077,9 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
DBFieldCodec<?, T, ?> codec = codecs.get(columnIndex);
Class<?> exp = codec.getValueType();
if (valueType != exp) {
if (fieldClass != exp) {
throw new IllegalArgumentException("Column " + schema.getFieldNames()[columnIndex] +
" is not of type " + valueType + "! It is " + exp);
" is not of type " + fieldClass + "! It is " + exp);
}
@SuppressWarnings("unchecked")
DBFieldCodec<K, T, ?> castCodec = (DBFieldCodec<K, T, ?>) codec;
@ -908,15 +1087,45 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
Direction.FORWARD);
}
/**
* Get the index for a given column
*
* <p>
* See {@link DBCachedObjectStoreFactory} for an example that includes use of an index
*
* @param <K> the type of the object field for the indexed column
* @param fieldClass the class specifying {@link K}
* @param column the indexed column
* @return the index
* @throws IllegalArgumentException if the column has a different type than {@link K}
*/
public <K> DBCachedObjectIndex<K, T> getIndex(Class<K> fieldClass, DBObjectColumn column) {
return getIndex(fieldClass, column.columnNumber);
}
/**
* Get the index for a given column by name
*
* <p>
* See {@link DBCachedObjectStoreFactory} for an example that includes use of an index
*
* @param <K> the type of the object field for the indexed column
* @param fieldClass the class specifying {@link K}
* @param columnName the name of the indexed column
* @return the index
* @throws IllegalArgumentException if the given column is not indexed
*/
public <K> DBCachedObjectIndex<K, T> getIndex(Class<K> fieldClass, String columnName) {
int columnIndex = getColumnByName(columnName);
return getIndex(fieldClass, columnIndex);
}
/**
* Delete the given object
*
* @param obj the object
* @return true if the object was removed, false for no effect
*/
public boolean delete(T obj) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return objects.typedRemove(obj) != null;
@ -927,6 +1136,12 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* Delete the object with the given key
*
* @param key the key
* @return true if the key was removed, false for no effect
*/
public T deleteKey(long key) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return keys.typedRemove(key);
@ -937,6 +1152,9 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* Clear the entire table
*/
public void deleteAll() {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
table.deleteAll();
@ -966,10 +1184,24 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* A variation of {@link Supplier} that allows {@link IOException} to pass through
*
* @param <U> the type of object supplied
*/
protected interface SupplierAllowsIOException<U> {
U get() throws IOException;
}
/**
* Invoke the given supplier with a lock, directing {@link IOException}s to the domain object
* adapter
*
* @param <U> the type of the result
* @param l the lock to hold during invocation
* @param supplier the supplier to invoke
* @return the result
*/
protected <U> U safe(Lock l, SupplierAllowsIOException<U> supplier) {
try (LockHold hold = LockHold.lock(l)) {
return supplier.get();
@ -980,6 +1212,12 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* Get the object having the given key
*
* @param key the key
* @return the object, or null
*/
public T getObjectAt(long key) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return objects.get(key);
@ -990,14 +1228,36 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
}
}
/**
* Get the key comparator
*
* @implNote this is probably vestigial, left from when we attempted to allow customization of
* the primary key. This currently just gives the natural ordering of longs.
*
* @return the comparator
*/
protected Comparator<? super Long> keyComparator() {
return KEY_COMPARATOR;
}
/**
* Provides access to the store as a {@link NavigableMap}.
*
* @return the map
*/
public DBCachedObjectStoreMap<T> asMap() {
return asForwardMap;
}
/**
* Search a column index for a single object having the given value
*
* @param columnIndex the indexed column's number
* @param field a field holding the value to seek
* @return the object, if found, or null
* @throws IOException if there's an issue reading the table
* @throws IllegalStateException if the object is not unique
*/
protected T findOneObject(int columnIndex, Field field) throws IOException {
// TODO: Support non-long keys, eventually.
Field[] found = table.findRecords(field, columnIndex);
@ -1010,12 +1270,29 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return getObjectAt(found[0].getLongValue());
}
/**
* Search a column index for all objects having the given value
*
* @param columnIndex the indexed column's number
* @param field a field holding the value to seek
* @return the collection of objects found, possibly empty but never null
* @throws IOException if there's an issue reading the table
*/
protected DBCachedObjectStoreFoundKeysValueCollection<T> findObjects(int columnIndex,
Field field) throws IOException {
Field[] found = table.findRecords(field, columnIndex);
return new DBCachedObjectStoreFoundKeysValueCollection<>(this, adapter, lock, found);
}
/**
* Search a column index and iterate over objects having the given value
*
* @param columnIndex the indexed column's number
* @param fieldRange required: the range to consider
* @param direction the direction of iteration
* @return the iterator, possibly empty but never null
* @throws IOException if there's an issue reading the table
*/
protected Iterator<T> iterator(int columnIndex, Range<Field> fieldRange, Direction direction)
throws IOException {
DirectedRecordIterator it =
@ -1023,18 +1300,39 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return objects.iterator(it);
}
/**
* For testing: check if the given key is in the cache
*
* @param key the key
* @return true if cached
*/
boolean isCached(long key) {
return cache.get(key) != null;
}
/**
* Get the read lock
*
* @return the lock
*/
public Lock readLock() {
return lock.readLock();
}
/**
* Get the write lock
*
* @return the lock
*/
public Lock writeLock() {
return lock.writeLock();
}
/**
* Get the read-write lock
*
* @return the lock
*/
public ReadWriteLock getLock() {
return lock;
}
@ -1047,6 +1345,7 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
/**
* Display useful information about this cached store
*
* <p>
* Please avoid calling this except for debugging.
*
* @return a string representation of the store's cache
@ -1059,6 +1358,13 @@ public class DBCachedObjectStore<T extends DBAnnotatedObject> implements ErrorHa
return builder.toString();
}
/**
* Invalidate this store's cache
*
* <p>
* This should be called whenever the table may have changed in a way not caused by the store
* itself, e.g., whenever {@link DBHandle#undo()} is called.
*/
public void invalidateCache() {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
cache.invalidate();

View file

@ -26,6 +26,15 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link Map#entrySet()} for
* {@link DBCachedObjectStore#asMap()}
*
* <p>
* The store acts as a map from object id to object, thus an entry has a long key and object value.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreEntrySet<T extends DBAnnotatedObject>
implements NavigableSet<Entry<Long, T>> {
protected final DBCachedObjectStore<T> store;

View file

@ -25,6 +25,14 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreEntrySet}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...).entrySet()}.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreEntrySubSet<T extends DBAnnotatedObject>
extends DBCachedObjectStoreEntrySet<T> {

View file

@ -33,21 +33,225 @@ import ghidra.util.database.annot.DBAnnotatedField.DefaultCodec;
import ghidra.util.database.err.NoDefaultCodecException;
import ghidra.util.exception.VersionException;
/**
* A factory for creating object stores for classes extending {@link DBAnnotatedObject}
*
* <p>
* See {@link DBAnnotatedObject} for more documentation, including an example object definition. To
* create a store, e.g., for {@code Person}:
*
* <pre>
* interface MyDomainObject {
* Person createPerson(String name, String address);
*
* Person getPerson(long id);
*
* Collection<? extends Person> getPeopleNamed(String name);
* }
*
* public class DBMyDomainObject extends DBCachedDomainObjectAdapter implements MyDomainObject {
* private final DBCachedObjectStoreFactory factory;
* private final DBCachedObjectStore<DBPerson> people;
* private final DBCachedObjectIndex<String, DBPerson> peopleByName;
*
* public DBMyDomainObject() { // Constructor parameters elided
* // super() invocation elided
* factory = new DBCachedObjectStoreFactory(this);
* try {
* people = factory.getOrCreateCachedStore(DBPerson.TABLE_NAME, DBPerson.class,
* DBPerson::new, false);
* peopleByName = people.getIndex(String.class, DBPerson.NAME_COLUMN);
* }
* catch (VersionException e) {
* // ...
* }
* catch (IOException e) {
* // ...
* }
* }
*
* &#64;Override
* public Person createPerson(String name, String address) {
* // Locking details elided
* DBPerson person = people.create();
* person.set(name, address);
* return person;
* }
*
* &#64;Override
* public Person getPerson(int id) {
* // Locking details elided
* return people.getAt(id);
* }
*
* &#64;Override
* public Collection<Person> getPeopleNamed(String name) {
* // Locking details elided
* return peopleByName.get(name);
* }
* }
* </pre>
*
* <p>
* The factory manages tables on behalf of the domain object, so it is typically the first thing
* constructed. In practice, complex domain objects should be composed of several managers, each of
* which constructs its own stores, but for simplicity in this example, we construct the people
* store in the domain object. This will check the schema and could throw a
* {@link VersionException}. Typically, immediately after constructing the store, all desired
* indexes of the store are retrieved. The domain object then provides API methods for creating and
* retrieving people. Providing direct API client access to the store from a domain object is highly
* discouraged.
*
* @implNote This class bears the responsibility of processing the {@link DBAnnotatedField},
* {@link DBAnnotatedColumn}, and {@link DBAnnotatedObjectInfo} annotations. The relevant
* entry point is {{@link #buildInfo(Class)}. It creates a {@link TableInfo} for the given
* class, which builds the schema for creating the {@link Table} that backs an object
* store for that class.
*/
public class DBCachedObjectStoreFactory {
/**
* A codec for encoding alternative data types
*
* <p>
* The database framework supports limited types of fields, each capable for storing a specific
* Java data type. A simple codec is provided for "encoding" each of the supported types into
* its corresponding {@link db.Field} type. For other types, additional custom codecs must be
* implemented. Custom codecs must be explicitly selected using the
* {@link DBAnnotatedField#codec()} attribute.
*
* <p>
* <b>NOTE:</b> When changing the implementation of a codec, keep in mind whether or not it
* implies a change to the schema of tables that use the codec. If it does, their schema
* versions, i.e., {@link DBAnnotatedObjectInfo#version()} should be incremented and
* considerations made for supporting upgrades.
*
* <p>
* In some cases, the codec may require context information from the containing object. This is
* facilitated via the {@link OT} type parameter. If no additional context is required,
* {@link DBAnnotatedObject} is sufficient. If context is required, then additional interfaces
* can be required via type intersection:
*
* <pre>
* public interface MyContext {
* // ...
* }
*
* public interface ContextProvider {
* MyContext getContext();
* }
*
* public static class MyDBFieldCodec<OT extends DBAnnotatedObject & ContextProvider> extends
* AbstractDBFieldCodec<MyType, OT, BinaryField> {
*
* public MyDBFieldCodec(Class<OT> objectType, Field field, int column) {
* super(MyType.class, objectType, BinaryField.class, field, column);
* }
*
* &#64;Override
* protected void doStore(OT obj, DBRecord record) {
* MyContext ctx = obj.getContext();
* // ...
* }
* // ...
* }
* </pre>
*
* <p>
* Note that this implementation uses {@link AbstractDBFieldCodec}, which is highly recommended.
* Whether or not the abstract codec is used, the constructor must have the signature
* {@code (Class<OT>, Field, int)}, which are the containing object's actual type, the field of
* the Java class whose values to encode, and the record column number into which to store those
* encoded values. The type variables {@link VT} and {@link FT} of the codec indicate it can
* encode values of type {@code MyType} into a byte array for storage into a
* {@link BinaryField}. See {@link ByteDBFieldCodec} for the simplest example with actual
* encoding and decoding implementations. To use the example codec in an object:
*
* <pre>
* &#64;DBAnnotatedObjectInfo(version = 1)
* public static class SomeObject extends DBAnnotatedObject implements ContextProvider {
* static final String MY_COLUMN_NAME = "My";
*
* &#64;DBAnnotatedColumn(MY_COLUMN_NAME)
* static DBObjectColumn MY_COLUMN;
*
* &#64;DBAnnotatedField(column = MY_COLUMN_NAME, codec = MyDBFieldCodec.class)
* private MyType my;
*
* // ...
*
* &#64;Override
* public MyContext getContext() {
* // ...
* }
* }
* </pre>
*
* <p>
* Notice that {@code SomeObject} must implement {@code ContextProvider}. This restriction is
* checked at runtime when the object store is created, but a compile-time annotation processor
* can check this restriction sooner. This has been implemented, at least in part, in the
* {@code AnnotationProcessor} project. It is recommended that at most one additional interface
* is required in by {@link OT}. If multiple contexts are required, consider declaring an
* interface that extends the multiple required interfaces. Alternatively, consider a new
* interface that provides one composite context.
*
* @param <VT> the type of the value encoded, i.e., the object field's Java type
* @param <OT> the upper bound on objects containing the field
* @param <FT> the type of the database field into which the value is encoded
*/
public interface DBFieldCodec<VT, OT extends DBAnnotatedObject, FT extends db.Field> {
/**
* Encode the field from the given object into the given record
*
* @param obj the source object
* @param record the destination record
*/
void store(OT obj, DBRecord record);
/**
* Encode the given field value into the given field
*
* @param value the value
* @param f the field
*/
void store(VT value, FT f);
/**
* Decode the field from the given record into the given object
*
* @param obj the destination object
* @param record the source record
*/
void load(OT obj, DBRecord record);
/**
* Get the type of values encoded and decoded
*
* @return the value type
*/
Class<VT> getValueType();
/**
* Get the upper bound on objects with fields using this codec
*
* @return the upper bound
*/
Class<OT> getObjectType();
/**
* Get the type of field storing the values
*
* @return the field type
*/
Class<FT> getFieldType();
/**
* Encode the given value into a new field
*
* @param value the value
* @return the field with the encoded value
*/
default FT encodeField(VT value) {
try {
FT field = getFieldType().getConstructor().newInstance();
@ -60,9 +264,22 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* Get the value from the object
*
* @param obj the source object
* @return the value
*/
VT getValue(OT obj);
}
/**
* An abstract implementation of {@link DBFieldCodec}
*
* <p>
* This reduces the implementation burden to {@link #doLoad(DBAnnotatedObject, DBRecord)},
* {@link #doStore(DBAnnotatedObject, DBRecord)}, and {@link #store(Object, db.Field)}.
*/
public static abstract class AbstractDBFieldCodec<VT, OT extends DBAnnotatedObject, FT extends db.Field>
implements DBFieldCodec<VT, OT, FT> {
protected final Class<VT> valueType;
@ -71,6 +288,15 @@ public class DBCachedObjectStoreFactory {
protected final Field field;
protected final int column;
/**
* Construct a codec
*
* @param valueType
* @param objectType
* @param fieldType
* @param field
* @param column
*/
public AbstractDBFieldCodec(Class<VT> valueType, Class<OT> objectType, Class<FT> fieldType,
Field field, int column) {
if (!field.getDeclaringClass().isAssignableFrom(objectType)) {
@ -133,18 +359,35 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* Set the value of the object
*
* @param obj the object whose field to set
* @param value the value to assign
* @throws IllegalArgumentException as in {@link Field#set(Object, Object)}
* @throws IllegalAccessException as in {@link Field#set(Object, Object)}
*/
protected void setValue(OT obj, VT value)
throws IllegalArgumentException, IllegalAccessException {
field.set(obj, value);
}
/**
* Same as {@link #store(DBAnnotatedObject, DBRecord)}, but permits exceptions
*/
protected abstract void doStore(OT obj, DBRecord record)
throws IllegalArgumentException, IllegalAccessException;
/**
* Same as {@link #load(DBAnnotatedObject, DBRecord), but permits exceptions
*/
protected abstract void doLoad(OT obj, DBRecord record)
throws IllegalArgumentException, IllegalAccessException;
}
/**
* The built-in codec for {@code boolean}
*/
public static class BooleanDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<Boolean, OT, BooleanField> {
public BooleanDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -169,6 +412,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@code byte}
*/
public static class ByteDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<Byte, OT, ByteField> {
public ByteDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -193,6 +439,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@code short}
*/
public static class ShortDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<Short, OT, ShortField> {
public ShortDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -217,6 +466,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@code int}
*/
public static class IntDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<Integer, OT, IntField> {
public IntDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -241,6 +493,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@code long}
*/
public static class LongDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<Long, OT, LongField> {
public LongDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -265,6 +520,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@link String}
*/
public static class StringDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<String, OT, StringField> {
public StringDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -289,6 +547,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@code byte[]}
*/
public static class ByteArrayDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<byte[], OT, BinaryField> {
public ByteArrayDBFieldCodec(Class<OT> objectType, Field field, int column) {
@ -313,6 +574,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@code long[]}
*/
public static class LongArrayDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<long[], OT, BinaryField> {
@ -361,6 +625,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* The built-in codec for {@link Enum}
*/
public static class EnumDBByteFieldCodec<OT extends DBAnnotatedObject, E extends Enum<E>>
extends AbstractDBFieldCodec<E, OT, ByteField> {
private final E[] consts;
@ -410,15 +677,50 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* Codec for a primitive type
*
* <p>
* This is used by {@link VariantDBFieldCodec} to encode primitive values. Sadly, the existing
* primitive field codecs cannot be used, since they write to fields directly. All these encode
* into byte buffers, since the variant codec uses {@link BinaryField}.
*
* @param <T> the type of values encoded
*/
public interface PrimitiveCodec<T> {
/**
* A byte value which identifies this codec's type as the selected type
*
* @return the selector
*/
byte getSelector();
/**
* Decode the value from the given buffer
*
* @param buffer the source buffer
* @return the value
*/
T decode(ByteBuffer buffer);
/**
* Encode the value into the given buffer
*
* @param buffer the destination buffer
* @param value the value
*/
void encode(ByteBuffer buffer, T value);
/**
* The the class describing {@link T}
*
* @return the class
*/
Class<T> getValueClass();
/**
* An abstract implementation of {@link PrimitiveCodec}
*/
abstract class AbstractPrimitiveCodec<T> implements PrimitiveCodec<T> {
static byte nextSelector = 0;
protected final byte selector = nextSelector++;
@ -439,6 +741,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* A implementation of {@link PrimitiveCodec} from lambdas or method references
*/
class SimplePrimitiveCodec<T> extends AbstractPrimitiveCodec<T> {
protected final Function<ByteBuffer, T> decode;
protected final BiConsumer<ByteBuffer, T> encode;
@ -461,11 +766,19 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* An implementation of an array codec, using its element codec, where elements can be
* primitives
*
* @param <E> the type of elements
* @param <T> the type of the value, i.e., would be {@code E[]}, except we want {@link E} to
* be primitive.
*/
class ArrayPrimitiveCodec<E, T> extends AbstractPrimitiveCodec<T> {
protected final PrimitiveCodec<E> elemCodec;
protected final Class<E> elemClass;
public <P> ArrayPrimitiveCodec(Class<T> valueClass, PrimitiveCodec<E> elemCodec) {
public ArrayPrimitiveCodec(Class<T> valueClass, PrimitiveCodec<E> elemCodec) {
super(valueClass);
assert valueClass.isArray();
this.elemCodec = elemCodec;
@ -495,6 +808,11 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* An implementation of an array codec, using its element codec, where elements are objects
*
* @param <E> the type of elements
*/
class ArrayObjectCodec<E> extends ArrayPrimitiveCodec<E, E[]> {
@SuppressWarnings("unchecked")
public ArrayObjectCodec(PrimitiveCodec<E> elemCodec) {
@ -503,6 +821,9 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* A codec which encodes length-value, using the (unbounded) codec for value
*/
class LengthBoundCodec<T> extends AbstractPrimitiveCodec<T> {
protected final PrimitiveCodec<T> unbounded;
@ -535,18 +856,30 @@ public class DBCachedObjectStoreFactory {
}
}
/*
* WARNING: Careful changing the order of these declarations, as this will change the
* selectors. Doing so would require a schema version bump of any table using the
* {@link VariantDBFieldCodec}.
*/
/** Codec for {@code boolean} */
PrimitiveCodec<Boolean> BOOL = new SimplePrimitiveCodec<>(Boolean.class,
buf -> buf.get() != 0, (buf, b) -> buf.put((byte) (b ? 1 : 0)));
/** Codec for {@code byte} */
PrimitiveCodec<Byte> BYTE =
new SimplePrimitiveCodec<>(Byte.class, ByteBuffer::get, ByteBuffer::put);
/** Codec for {@code char} */
PrimitiveCodec<Character> CHAR =
new SimplePrimitiveCodec<>(Character.class, ByteBuffer::getChar, ByteBuffer::putChar);
/** Codec for {@code short} */
PrimitiveCodec<Short> SHORT =
new SimplePrimitiveCodec<>(Short.class, ByteBuffer::getShort, ByteBuffer::putShort);
/** Codec for {@code int} */
PrimitiveCodec<Integer> INT =
new SimplePrimitiveCodec<>(Integer.class, ByteBuffer::getInt, ByteBuffer::putInt);
/** Codec for {@code long} */
PrimitiveCodec<Long> LONG =
new SimplePrimitiveCodec<>(Long.class, ByteBuffer::getLong, ByteBuffer::putLong);
/** Codec for {@link String} */
PrimitiveCodec<String> STRING = new AbstractPrimitiveCodec<>(String.class) {
final Charset cs = Charset.forName("UTF-8");
@ -568,7 +901,9 @@ public class DBCachedObjectStoreFactory {
enc.encode(CharBuffer.wrap(value), buffer, true);
}
};
/** Codec for {@code boolean[]} */
PrimitiveCodec<boolean[]> BOOL_ARR = new ArrayPrimitiveCodec<>(boolean[].class, BOOL);
/** Codec for {@code byte[]} */
PrimitiveCodec<byte[]> BYTE_ARR = new AbstractPrimitiveCodec<>(byte[].class) {
@Override
public byte[] decode(ByteBuffer buffer) {
@ -582,10 +917,15 @@ public class DBCachedObjectStoreFactory {
buffer.put(value);
}
};
/** Codec for {@code char[]} */
PrimitiveCodec<char[]> CHAR_ARR = new ArrayPrimitiveCodec<>(char[].class, CHAR);
/** Codec for {@code short[]} */
PrimitiveCodec<short[]> SHORT_ARR = new ArrayPrimitiveCodec<>(short[].class, SHORT);
/** Codec for {@code int[]} */
PrimitiveCodec<int[]> INT_ARR = new ArrayPrimitiveCodec<>(int[].class, INT);
/** Codec for {@code long[]} */
PrimitiveCodec<long[]> LONG_ARR = new ArrayPrimitiveCodec<>(long[].class, LONG);
/** Codec for {@code String[]} */
PrimitiveCodec<String[]> STRING_ARR =
new ArrayObjectCodec<>(new LengthBoundCodec<>(STRING));
@ -597,6 +937,14 @@ public class DBCachedObjectStoreFactory {
.stream()
.collect(Collectors.toMap(c -> c.getValueClass(), c -> c));
/**
* Get the codec for the given type
*
* @param <T> the type
* @param cls the class describing {@link T}
* @return the codec
* @throws IllegalArgumentException if the type is not supported
*/
static <T> PrimitiveCodec<T> getCodec(Class<T> cls) {
@SuppressWarnings("unchecked")
PrimitiveCodec<T> obj = (PrimitiveCodec<T>) CODECS_BY_CLASS.get(cls);
@ -606,6 +954,13 @@ public class DBCachedObjectStoreFactory {
return obj;
}
/**
* Get the codec for the given selector
*
* @param sel the selector
* @return the codec
* @throws IllegalArgumentException if the selector is unknown
*/
static PrimitiveCodec<?> getCodec(byte sel) {
PrimitiveCodec<?> obj = CODECS_BY_SELECTOR.get(sel);
if (obj == null) {
@ -615,15 +970,27 @@ public class DBCachedObjectStoreFactory {
}
}
public static abstract class AbstractVariantDBFieldCodec<OT extends DBAnnotatedObject>
/**
* A custom codec for field of "variant" type
*
* <p>
* This is suitable for use on fields of type {@link Object}; however, only certain types can
* actually be encoded. The encoding uses a 1-byte type selector followed by the byte-array
* encoded value.
*/
public static class VariantDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractDBFieldCodec<Object, OT, BinaryField> {
public AbstractVariantDBFieldCodec(Class<OT> objectType, Field field, int column) {
public VariantDBFieldCodec(Class<OT> objectType, Field field, int column) {
super(Object.class, objectType, BinaryField.class, field, column);
}
protected abstract PrimitiveCodec<?> getPrimitiveCodec(Class<?> cls);
protected PrimitiveCodec<?> getPrimitiveCodec(Class<?> cls) {
return PrimitiveCodec.getCodec(cls);
}
protected abstract PrimitiveCodec<?> getPrimitiveCodec(OT obj, byte sel);
protected PrimitiveCodec<?> getPrimitiveCodec(OT obj, byte sel) {
return PrimitiveCodec.getCodec(sel);
}
protected byte[] encode(Object value) {
if (value == null) {
@ -676,30 +1043,28 @@ public class DBCachedObjectStoreFactory {
}
}
public static class VariantDBFieldCodec<OT extends DBAnnotatedObject>
extends AbstractVariantDBFieldCodec<OT> {
public VariantDBFieldCodec(Class<OT> objectType, Field field, int column) {
super(objectType, field, column);
}
@Override
protected PrimitiveCodec<?> getPrimitiveCodec(Class<?> cls) {
return PrimitiveCodec.getCodec(cls);
}
@Override
protected PrimitiveCodec<?> getPrimitiveCodec(OT obj, byte sel) {
return PrimitiveCodec.getCodec(sel);
}
}
/**
* The information needed to construct a {@link Table} and store objects into it
*
* @param <OT> the type of object stored in the table
*/
private static class TableInfo<OT extends DBAnnotatedObject> {
public final Schema schema;
public final int[] indexColumns;
public final ArrayList<DBFieldCodec<?, OT, ?>> codecs;
/**
* Derive the table information
*
* @param objectType the class of objects being described
* @param schemaVersion the schema version as given in
* {@link DBAnnotatedObjectInfo#version()}
* @param fieldsByColumnName the class fields by user-defined column name
* @param indexFields the fields selected for table indexes
* @param sparseFields the fields selected for sparse storage
*/
TableInfo(Class<OT> objectType, int schemaVersion, Map<String, Field> fieldsByColumnName,
Collection<Field> indexFields) {
Collection<Field> indexFields, Collection<Field> sparseFields) {
codecs = new ArrayList<>(fieldsByColumnName.size());
List<Integer> indexCols = new ArrayList<>(indexFields.size());
SchemaBuilder builder = new SchemaBuilder();
@ -714,16 +1079,19 @@ public class DBCachedObjectStoreFactory {
indexCols.add(next);
}
codecs.add(codec);
builder.field(ent.getKey(), codec.getFieldType());
builder.field(ent.getKey(), codec.getFieldType(), sparseFields.contains(field));
}
schema = builder.build();
indexColumns = new int[indexCols.size()];
for (int i = 0; i < indexColumns.length; i++) {
indexColumns[i] = indexCols.get(i);
}
indexColumns = SchemaBuilder.toIntArray(indexCols);
}
/**
* Initialize the static {@link DBObjectColumn} fields marked with {@link DBAnnotatedColumn}
*
* @param objectType the clas of objects being described
* @param numbersByName the assigned column numbers by user-defined name
*/
void writeColumnNumbers(Class<? extends DBAnnotatedObject> objectType,
Map<String, Integer> numbersByName) {
Class<?> superType = objectType.getSuperclass();
@ -771,6 +1139,11 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* Initialize the static {@link DBObjectColumn} fields marked with {@link DBAnnotatedColumn}
*
* @param objectType the clas of objects being described
*/
void writeColumnNumbers(Class<? extends DBAnnotatedObject> objectType) {
Map<String, Integer> numbersByName = new HashMap<>();
String[] names = schema.getFieldNames();
@ -781,9 +1154,19 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* A cache of derived table information by class
*/
private static final Map<Class<? extends DBAnnotatedObject>, TableInfo<?>> INFO_MAP =
new HashMap<>();
/**
* Get a built-in codec for a field of the given type
*
* @param type the type
* @return the built-in codec
* @throws NoDefaultCodecException if there is no built-in codec for the field
*/
private static Class<?> getDefaultCodecClass(Class<?> type) {
if (type == boolean.class || type == Boolean.class) {
return BooleanDBFieldCodec.class;
@ -817,6 +1200,20 @@ public class DBCachedObjectStoreFactory {
type + " does not have a default codec. Please specify a codec.");
}
/**
* Construct the codec for the given field
*
* <p>
* This adheres to the custom codec, if specified on the fields annotation.
*
* @param <OT> the type of objects being described
* @param objectType the class describing {@link OT}
* @param field the field to encode and decode
* @param column the column number in the record
* @return the codec
* @throws IllegalArgumentException if the selected codec's constructor does not have the
* required signature
*/
@SuppressWarnings({ "unchecked" })
private static <OT extends DBAnnotatedObject> DBFieldCodec<?, OT, ?> makeCodec(
Class<OT> objectType, Field field, int column) throws IllegalArgumentException {
@ -844,19 +1241,36 @@ public class DBCachedObjectStoreFactory {
}
}
/**
* Get the table information for the given class
*
* @param <T> the type of objects to store in a table
* @param cls the class describing {@link T}
* @return the table information
*/
@SuppressWarnings("unchecked")
public static <T extends DBAnnotatedObject> TableInfo<T> getInfo(Class<T> cls) {
private static <T extends DBAnnotatedObject> TableInfo<T> getInfo(Class<T> cls) {
synchronized (INFO_MAP) {
return (TableInfo<T>) INFO_MAP.computeIfAbsent(cls,
DBCachedObjectStoreFactory::buildInfo);
}
}
/**
* Get the codecs for the given class
*
* @param <OT> the type of objects to store in the table
* @param objectType the class describing {@link OT}
* @return the codecs, in column order
*/
static <OT extends DBAnnotatedObject> List<DBFieldCodec<?, OT, ?>> getCodecs(
Class<OT> objectType) {
return getInfo(objectType).codecs;
}
/**
* The non-cached implementation of {@link #getInfo(Class)}
*/
private static <OT extends DBAnnotatedObject> TableInfo<OT> buildInfo(Class<OT> objectType) {
DBAnnotatedObjectInfo info = objectType.getAnnotation(DBAnnotatedObjectInfo.class);
if (info == null) {
@ -867,19 +1281,29 @@ public class DBCachedObjectStoreFactory {
Map<String, Field> fields = new LinkedHashMap<>();
List<Field> indexFields = new ArrayList<>();
collectFields(objectType, fields, indexFields);
List<Field> sparseFields = new ArrayList<>();
collectFields(objectType, fields, indexFields, sparseFields);
TableInfo<OT> tableInfo = new TableInfo<>(objectType, info.version(), fields, indexFields);
TableInfo<OT> tableInfo =
new TableInfo<>(objectType, info.version(), fields, indexFields, sparseFields);
tableInfo.writeColumnNumbers(objectType);
return tableInfo;
}
/**
* Collect the fields of the given class, recursively, starting with its super class
*
* @param cls the class
* @param fields a map to receive the fields
* @param indexFields a list for receiving fields to be indexed
* @param sparseFields a list for receiving fields to have sparse storage
*/
private static void collectFields(Class<?> cls, Map<String, Field> fields,
List<Field> indexFields) {
List<Field> indexFields, List<Field> sparseFields) {
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
collectFields(superclass, fields, indexFields);
collectFields(superclass, fields, indexFields, sparseFields);
}
for (Field f : cls.getDeclaredFields()) {
DBAnnotatedField annotation = f.getAnnotation(DBAnnotatedField.class);
@ -899,17 +1323,39 @@ public class DBCachedObjectStoreFactory {
if (annotation.indexed()) {
indexFields.add(f);
}
if (annotation.sparse()) {
sparseFields.add(f);
}
}
}
private final DBHandle handle;
private final DBCachedDomainObjectAdapter adapter;
/**
* Construct an object store factory
*
* @param adapter the object whose tables to manage
*/
public DBCachedObjectStoreFactory(DBCachedDomainObjectAdapter adapter) {
this.handle = adapter.getDBHandle();
this.adapter = adapter;
}
/**
* Get or create the table needed to store objects of the given class
*
* <p>
* See {@link #getOrCreateCachedStore(String, Class, DBAnnotatedObjectFactory, boolean)}
*
* @param name the table name
* @param cls the type of objects to store
* @param upgradable true if {@link VersionException}s should be marked upgradable when an
* existing table's version is earlier than expected
* @return the table
* @throws IOException if there's an issue accessing the database
* @throws VersionException if an existing table's version does not match that expected
*/
public Table getOrCreateTable(String name, Class<? extends DBAnnotatedObject> cls,
boolean upgradable) throws IOException, VersionException {
// TODO: System of upgraders
@ -926,6 +1372,19 @@ public class DBCachedObjectStoreFactory {
return table;
}
/**
* Get or create a cached store of objects of the given class
*
* @param <T> the type of objects in the store
* @param tableName the table name
* @param cls the class describing {@link T}
* @param factory the object's constructor, usually a method reference or lambda
* @param upgradable true if {@link VersionException}s should be marked upgradable when an
* existing table's version is earlier than expected
* @return the table
* @throws IOException if there's an issue accessing the database
* @throws VersionException if an existing table's version does not match that expected
*/
public <T extends DBAnnotatedObject> DBCachedObjectStore<T> getOrCreateCachedStore(
String tableName, Class<T> cls, DBAnnotatedObjectFactory<T> factory, boolean upgradable)
throws VersionException, IOException {

View file

@ -26,19 +26,24 @@ import db.Field;
import db.util.ErrorHandler;
import ghidra.util.LockHold;
/**
* This provides the implementation of {@link DBCachedObjectIndex#get(Object)}
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreFoundKeysValueCollection<T extends DBAnnotatedObject>
implements Collection<T> {
protected final DBCachedObjectStore<T> store;
protected final ErrorHandler errHandler;
protected final ReadWriteLock lock;
protected final Set<Long> keys;
protected final List<Long> keys;
public DBCachedObjectStoreFoundKeysValueCollection(DBCachedObjectStore<T> store,
ErrorHandler errHandler, ReadWriteLock lock, Field[] keys) {
this.store = store;
this.errHandler = errHandler;
this.lock = lock;
this.keys = Stream.of(keys).map(Field::getLongValue).collect(Collectors.toSet());
this.keys = Stream.of(keys).map(Field::getLongValue).collect(Collectors.toList());
}
@Override

View file

@ -25,6 +25,9 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link Map#keySet()} for {@link DBCachedObjectStore#asMap()}
*/
public class DBCachedObjectStoreKeySet implements NavigableSet<Long> {
protected final DBCachedObjectStore<?> store;
protected final ErrorHandler errHandler;

View file

@ -24,6 +24,13 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreKeySubSet}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...).keySet()} or
* {@code map.keySet().subSet(...)}.
*/
public class DBCachedObjectStoreKeySubSet extends DBCachedObjectStoreKeySet {
protected final Range<Long> keyRange;

View file

@ -25,6 +25,16 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link DBCachedObjectStore#asMap()}
*
* <p>
* This implements a map from object id (long) to object. Objects cannot be added directly to this
* map, e.g., {@link #put(Long, DBAnnotatedObject)} is not supported. Instead use
* {@link DBCachedObjectStore#create(long)}.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreMap<T extends DBAnnotatedObject> implements NavigableMap<Long, T> {
protected final DBCachedObjectStore<T> store;
protected final ErrorHandler errHandler;

View file

@ -22,6 +22,14 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreMap}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...)}.
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreSubMap<T extends DBAnnotatedObject>
extends DBCachedObjectStoreMap<T> {
protected final Range<Long> keyRange;

View file

@ -15,13 +15,17 @@
*/
package ghidra.util.database;
import java.util.Collection;
import java.util.Iterator;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This provides the implementation of {@link Map#values()} for {@link DBCachedObjectStore#asMap()}
*
* @param <T> the type of objects in the store
*/
public class DBCachedObjectStoreValueCollection<T extends DBAnnotatedObject>
implements Collection<T> {
protected final DBCachedObjectStore<T> store;

View file

@ -24,6 +24,12 @@ import com.google.common.collect.Range;
import db.util.ErrorHandler;
import ghidra.util.database.DirectedIterator.Direction;
/**
* This is the sub-ranged form of {@link DBCachedObjectStoreValueCollection}
*
* <p>
* For example, this can be obtained via {@code store.asMap().subMap(...).values()}.
*/
public class DBCachedObjectStoreValueSubCollection<T extends DBAnnotatedObject>
extends DBCachedObjectStoreValueCollection<T> {
protected final Range<Long> keyRange;

View file

@ -18,6 +18,18 @@ package ghidra.util.database;
import java.util.ArrayList;
import java.util.List;
import ghidra.util.database.annot.DBAnnotatedColumn;
/**
* An opaque handle to the column backing an object field
*
* <p>
* Each should be declared as a static field of the same class whose field it describes, probably
* with package-only access. Each must also be annotated with {@link DBAnnotatedColumn}. For an
* example, see the documentation of {@link DBAnnotatedObject}. The annotated field receives its
* value the first time a store is created for the containing class. Until then, it is
* uninitialized.
*/
public class DBObjectColumn {
static List<DBObjectColumn> instances = new ArrayList<>(20);

View file

@ -17,6 +17,9 @@ package ghidra.util.database;
import db.DBConstants;
/**
* An enum, providing a type-safe version of {@link DBConstants}.
*/
public enum DBOpenMode {
CREATE(DBConstants.CREATE),
UPDATE(DBConstants.UPDATE),

View file

@ -1,56 +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.util.database;
import java.io.IOException;
import db.DBHandle;
public class DBTransaction implements AutoCloseable {
public static DBTransaction start(DBHandle handle, boolean commitByDefault) {
long tid = handle.startTransaction();
return new DBTransaction(handle, tid, commitByDefault);
}
private final DBHandle handle;
private final long tid;
private boolean commit;
private boolean open = true;
private DBTransaction(DBHandle handle, long tid, boolean commitByDefault) {
this.handle = handle;
this.tid = tid;
this.commit = commitByDefault;
}
public void abort() throws IOException {
open = false;
handle.endTransaction(tid, false);
}
public void commit() throws IOException {
open = false;
handle.endTransaction(tid, true);
}
@Override
public void close() throws IOException {
if (open) {
handle.endTransaction(tid, commit);
}
}
}

View file

@ -20,18 +20,55 @@ import java.io.IOException;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
public interface DirectedIterator<T> {
public enum Direction {
FORWARD, BACKWARD;
import db.Table;
static Direction reverse(Direction direction) {
if (direction == FORWARD) {
/**
* An iterator over some component of a {@link Table}
*
* @param <T> the type of the component, i.e., a key or record
*/
public interface DirectedIterator<T> {
/**
* The direction of iteration
*/
public enum Direction {
FORWARD {
@Override
Direction reverse() {
return BACKWARD;
}
},
BACKWARD {
@Override
Direction reverse() {
return FORWARD;
}
};
/**
* Get the reverse of this direction
*
* @return the reverse
*/
abstract Direction reverse();
/**
* Get the reverse of the given direction
*
* @param direction the direction
* @return the reverse
*/
static Direction reverse(Direction direction) {
return direction.reverse();
}
}
/**
* Get the discrete lower bound of the given range
*
* @param range the range
* @return the lower bound
*/
static long toIteratorMin(Range<Long> range) {
if (range == null) {
return Long.MIN_VALUE;
@ -47,6 +84,12 @@ public interface DirectedIterator<T> {
}
}
/**
* Get the discrete upper bound of the given range
*
* @param range the range
* @return the upper bound
*/
static long toIteratorMax(Range<Long> range) {
if (range == null) {
return Long.MAX_VALUE;
@ -62,17 +105,51 @@ public interface DirectedIterator<T> {
}
}
/**
* Compute the effective starting point for a forward iterator starting at the given bound
*
* @param range the range describing a limited view of keys
* @param bound the starting key
* @param inclusive whether the starting key is included
* @return the starting point, inclusive
*/
static long clampLowerBound(Range<Long> range, long bound, boolean inclusive) {
return Math.max(toIteratorMin(range), inclusive ? bound : bound + 1);
}
/**
* Compute the effective starting point for a backward iterator starting at the given bound
*
* @param range the range describing a limited view of keys
* @param bound the starting key
* @param inclusive whether the starting key is included
* @return the starting point, inclusive
*/
static long clampUpperBound(Range<Long> range, long bound, boolean inclusive) {
return Math.min(toIteratorMax(range), inclusive ? bound : bound - 1);
}
/**
* Check if the table has another record
*
* @return true if so
* @throws IOException if the table cannot be read
*/
boolean hasNext() throws IOException;
/**
* Get the component of the next record
*
* @return the component
* @throws IOException if the table cannot be read
*/
T next() throws IOException;
/**
* Delete the current record
*
* @return true if successful
* @throws IOException if the table cannot be accessed
*/
boolean delete() throws IOException;
}

View file

@ -21,7 +21,19 @@ import com.google.common.collect.Range;
import db.Table;
/**
* An iterator over keys of a table
*/
public interface DirectedLongKeyIterator extends DirectedIterator<Long> {
/**
* Get an iterator over the table, restricted to the given range, in the given direction
*
* @param table the table
* @param keyRange the limited range
* @param direction the direction
* @return the iterator
* @throws IOException if the table cannot be read
*/
public static AbstractDirectedLongKeyIterator getIterator(Table table, Range<Long> keyRange,
Direction direction) throws IOException {
long min = DirectedIterator.toIteratorMin(keyRange);

View file

@ -22,8 +22,20 @@ import com.google.common.collect.Range;
import db.*;
/**
* An iterator over records of a table
*/
public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
/**
* Get an iterator over the table, restricted to the given range of keys, in the given direction
*
* @param table the table
* @param keyRange the limited range
* @param direction the direction
* @return the iterator
* @throws IOException if the table cannot be read
*/
public static AbstractDirectedRecordIterator getIterator(Table table, Range<Long> keyRange,
Direction direction) throws IOException {
long min = DirectedIterator.toIteratorMin(keyRange);
@ -34,6 +46,13 @@ public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
return new BackwardRecordIterator(table.iterator(min, max, max));
}
/**
* Given an iterator over a closed range. Change its behavior to exclude the lower bound
*
* @param it the iterator over the closed range
* @param columnIndex the column number whose index being iterated
* @param exclude the lower bound to be excluded
*/
private static DirectedRecordIterator applyBegFilter(DirectedRecordIterator it, int columnIndex,
Field exclude) throws IOException {
return new DirectedRecordIterator() {
@ -71,6 +90,13 @@ public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
};
}
/**
* Given an iterator over a closed range. Change its behavior to exclude the upper bound
*
* @param it the iterator over the closed range
* @param columnIndex the column number whose index being iterated
* @param exclude the upper bound to be excluded
*/
private static DirectedRecordIterator applyEndFilter(DirectedRecordIterator it, int columnIndex,
Field exclude) throws IOException {
return new DirectedRecordIterator() {
@ -108,6 +134,17 @@ public interface DirectedRecordIterator extends DirectedIterator<DBRecord> {
return it;
}
/**
* Get an iterator over the table using a given index, restricted to the given range of values,
* in the given direction
*
* @param table the table
* @param columnIndex the column number of the index
* @param fieldRange the limited range
* @param direction the direction
* @return the iterator
* @throws IOException if the table cannot be read
*/
public static DirectedRecordIterator getIndexIterator(Table table, int columnIndex,
Range<Field> fieldRange, Direction direction) throws IOException {
Field lower = fieldRange.hasLowerBound() ? fieldRange.lowerEndpoint() : null;

View file

@ -19,6 +19,10 @@ import java.io.IOException;
import db.DBLongIterator;
/**
* A wrapper of {@link DBLongIterator} that runs it forward and implements
* {@link DirectedLongKeyIterator}
*/
public class ForwardLongKeyIterator extends AbstractDirectedLongKeyIterator {
public ForwardLongKeyIterator(DBLongIterator it) {
super(it);

View file

@ -20,6 +20,10 @@ import java.io.IOException;
import db.DBRecord;
import db.RecordIterator;
/**
* A wrapper of {@link RecordIterator} that runs it forward and implements
* {@link DirectedRecordIterator}
*/
public class ForwardRecordIterator extends AbstractDirectedRecordIterator {
public ForwardRecordIterator(RecordIterator it) {
super(it);

Some files were not shown because too many files have changed in this diff Show more