mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-29 05:38:48 +08:00
GP-2437: Change DBAddressFieldCodec to use FixedField10
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -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;
|
||||
}
|
||||
|
||||
+12
@@ -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;
|
||||
}
|
||||
|
||||
+4
@@ -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);
|
||||
|
||||
+4
@@ -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);
|
||||
|
||||
+162
-26
@@ -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 {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* @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
|
||||
* @DBAnnotatedColumn(NAME_COLUMN_NAME)
|
||||
* static DBObjectColumn NAME_COLUMN;
|
||||
* @DBAnnotatedColumn(ADDRESS_COLUMN_NAME)
|
||||
* static DBObjectColumn ADDRESS_COLUMN;
|
||||
*
|
||||
* // Column-backed fields
|
||||
* @DBAnnotatedField(column = NAME_COLUMN_NAME, indexed = true)
|
||||
* private String name;
|
||||
* @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());
|
||||
}
|
||||
|
||||
+5
@@ -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);
|
||||
}
|
||||
|
||||
+17
@@ -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;
|
||||
}
|
||||
|
||||
+331
-29
File diff suppressed because it is too large
Load Diff
+373
-67
File diff suppressed because it is too large
Load Diff
+9
@@ -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;
|
||||
|
||||
+8
@@ -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> {
|
||||
|
||||
|
||||
+492
-33
File diff suppressed because it is too large
Load Diff
+7
-2
@@ -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
|
||||
|
||||
+3
@@ -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;
|
||||
|
||||
+7
@@ -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;
|
||||
|
||||
|
||||
+10
@@ -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;
|
||||
|
||||
+8
@@ -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;
|
||||
|
||||
+6
-2
@@ -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;
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
-6
@@ -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;
|
||||
}
|
||||
return FORWARD;
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
+12
@@ -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);
|
||||
|
||||
+37
@@ -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;
|
||||
|
||||
+4
@@ -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);
|
||||
|
||||
+4
@@ -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);
|
||||
|
||||
@@ -17,24 +17,22 @@ package ghidra.util.database;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.framework.data.DomainObjectAdapterDB;
|
||||
import db.Table;
|
||||
|
||||
/**
|
||||
* Enough information to uniquely identify a trace object
|
||||
* An opaque handle uniquely identifying a database-backed object
|
||||
*/
|
||||
public class ObjectKey implements Comparable<ObjectKey> {
|
||||
|
||||
private final DomainObjectAdapterDB adapter;
|
||||
private final String tableName;
|
||||
private final Table table;
|
||||
private final long key;
|
||||
|
||||
private final int hash;
|
||||
|
||||
public ObjectKey(DomainObjectAdapterDB adapter, String tableName, long key) {
|
||||
this.adapter = adapter;
|
||||
this.tableName = tableName;
|
||||
public ObjectKey(Table table, long key) {
|
||||
this.table = table;
|
||||
this.key = key;
|
||||
this.hash = Objects.hash(System.identityHashCode(adapter), tableName, key);
|
||||
this.hash = Objects.hash(System.identityHashCode(table), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,10 +41,7 @@ public class ObjectKey implements Comparable<ObjectKey> {
|
||||
return false;
|
||||
}
|
||||
ObjectKey that = (ObjectKey) obj;
|
||||
if (this.adapter != that.adapter) {
|
||||
return false;
|
||||
}
|
||||
if (!(Objects.equals(this.tableName, that.tableName))) {
|
||||
if (this.table != that.table) {
|
||||
return false;
|
||||
}
|
||||
if (this.key != that.key) {
|
||||
@@ -63,16 +58,8 @@ public class ObjectKey implements Comparable<ObjectKey> {
|
||||
@Override
|
||||
public int compareTo(ObjectKey that) {
|
||||
int result;
|
||||
if (this.adapter != that.adapter) {
|
||||
result = this.adapter.getName().compareTo(that.adapter.getName());
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
return System.identityHashCode(this.adapter) - System.identityHashCode(that.adapter);
|
||||
}
|
||||
result = this.tableName.compareTo(that.tableName);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
if (this.table != that.table) {
|
||||
return System.identityHashCode(this.table) - System.identityHashCode(that.table);
|
||||
}
|
||||
result = Long.compareUnsigned(this.key, that.key);
|
||||
if (result != 0) {
|
||||
|
||||
@@ -22,7 +22,38 @@ import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import db.*;
|
||||
|
||||
/**
|
||||
* A builder for {@link Schema}
|
||||
*
|
||||
* <p>
|
||||
* Provides a more fluent syntax for creating table schemas. For example:
|
||||
*
|
||||
* <pre>
|
||||
* new Schema(1, StringField.class, "UUID",
|
||||
* new Class[] { StringField.class, IntField.class }, new String[] { "Name", "Flags" },
|
||||
* new int[] { 1 });
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Can be expressed using the builder:
|
||||
*
|
||||
* <pre>
|
||||
* new SchemaBuilder().keyField("UUID", StringField.class)
|
||||
* .field("Name", StringField.class)
|
||||
* .field("Flags", IntField.class, true)
|
||||
* .build();
|
||||
* </pre>
|
||||
*/
|
||||
public class SchemaBuilder {
|
||||
|
||||
public static int[] toIntArray(List<Integer> list) {
|
||||
int[] arr = new int[list.size()];
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
arr[i] = list.get(i);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static int getColumnIndex(Schema schema, String name) {
|
||||
return ArrayUtils.indexOf(schema.getFieldNames(), name);
|
||||
}
|
||||
@@ -30,10 +61,11 @@ public class SchemaBuilder {
|
||||
private int version = 0;
|
||||
private String keyFieldName = "Key";
|
||||
private Class<? extends Field> keyFieldClass = LongField.class;
|
||||
private List<String> fieldNames = new ArrayList<>();
|
||||
private List<Class<? extends Field>> fieldClasses = new ArrayList<>();
|
||||
private final List<String> fieldNames = new ArrayList<>();
|
||||
private final List<Class<? extends Field>> fieldClasses = new ArrayList<>();
|
||||
private final List<Integer> sparseColumns = new ArrayList<>();
|
||||
|
||||
public SchemaBuilder version(@SuppressWarnings("hiding") int version) {
|
||||
public SchemaBuilder version(int version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
@@ -44,12 +76,20 @@ public class SchemaBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public SchemaBuilder field(String name, Class<? extends Field> cls) {
|
||||
public SchemaBuilder field(String name, Class<? extends Field> cls, boolean sparse) {
|
||||
int index = fieldCount();
|
||||
this.fieldNames.add(name);
|
||||
this.fieldClasses.add(cls);
|
||||
if (sparse) {
|
||||
this.sparseColumns.add(index);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public SchemaBuilder field(String name, Class<? extends Field> cls) {
|
||||
return field(name, cls, false);
|
||||
}
|
||||
|
||||
public int fieldCount() {
|
||||
return fieldNames.size();
|
||||
}
|
||||
@@ -57,6 +97,6 @@ public class SchemaBuilder {
|
||||
public Schema build() {
|
||||
return new Schema(version, keyFieldClass, keyFieldName,
|
||||
fieldClasses.toArray(new Class[fieldClasses.size()]),
|
||||
fieldNames.toArray(new String[fieldNames.size()]));
|
||||
fieldNames.toArray(new String[fieldNames.size()]), toIntArray(sparseColumns));
|
||||
}
|
||||
}
|
||||
|
||||
+190
-23
@@ -15,51 +15,132 @@
|
||||
*/
|
||||
package ghidra.util.database;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import db.DBHandle;
|
||||
import db.NoTransactionException;
|
||||
import db.util.ErrorHandler;
|
||||
import ghidra.framework.model.AbortedTransactionListener;
|
||||
import ghidra.framework.model.UndoableDomainObject;
|
||||
import ghidra.program.model.data.DataTypeManager;
|
||||
import ghidra.program.model.listing.ProgramUserData;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Provides syntax for opening a database transaction using a try-with-resources block
|
||||
*
|
||||
* <p>
|
||||
* For example, using {@link UndoableDomainObject#startTransaction(String)} directly:
|
||||
*
|
||||
* <pre>
|
||||
* int txid = program.startTransaction("Do a thing");
|
||||
* try {
|
||||
* // ... Do that thing
|
||||
* }
|
||||
* finally {
|
||||
* program.endTransaction(txid, true);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Can be expressed using an undoable transaction instead:
|
||||
*
|
||||
* <pre>
|
||||
* try (UndoableTransaction txid = UndoableTransaction.start(program, "Do a thing", true)) {
|
||||
* // ... Do that thing
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public interface UndoableTransaction extends AutoCloseable {
|
||||
public static UndoableTransaction start(UndoableDomainObject domainObject, String description,
|
||||
boolean commitByDefault) {
|
||||
/**
|
||||
* Open a transaction directly on a database handle
|
||||
*
|
||||
* @param handle the handle
|
||||
* @param errHandler a handler for database errors, usually the domain object
|
||||
* @return the transaction handle
|
||||
*/
|
||||
public static UndoableTransaction start(DBHandle handle, ErrorHandler errHandler) {
|
||||
long tid = handle.startTransaction();
|
||||
return new DBHandleUndoableTransaction(handle, tid, errHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a transaction on a domain object
|
||||
*
|
||||
* @param domainObject the domain object
|
||||
* @param description a description of the change
|
||||
* @return the transaction handle
|
||||
*/
|
||||
public static UndoableTransaction start(UndoableDomainObject domainObject, String description) {
|
||||
int tid = domainObject.startTransaction(description);
|
||||
return new DomainObjectUndoableTransaction(domainObject, tid, commitByDefault);
|
||||
return new DomainObjectUndoableTransaction(domainObject, tid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a transaction on a domain object
|
||||
*
|
||||
* <p>
|
||||
* Even if this transaction is committed, if a sub-transaction is aborted, this transaction
|
||||
* could become aborted, too. The listener can be used to detect this situation.
|
||||
*
|
||||
* @param domainObject the domain object
|
||||
* @param description a description of the change
|
||||
* @param listener a listener for aborted transactions
|
||||
* @param commitByDefault true to commit at the end of the block
|
||||
* @return the transaction handle
|
||||
*/
|
||||
public static UndoableTransaction start(UndoableDomainObject domainObject, String description,
|
||||
AbortedTransactionListener listener, boolean commitByDefault) {
|
||||
AbortedTransactionListener listener) {
|
||||
int tid = domainObject.startTransaction(description, listener);
|
||||
return new DomainObjectUndoableTransaction(domainObject, tid, commitByDefault);
|
||||
return new DomainObjectUndoableTransaction(domainObject, tid);
|
||||
}
|
||||
|
||||
public static UndoableTransaction start(DataTypeManager dataTypeManager, String description,
|
||||
boolean commitByDefault) {
|
||||
/**
|
||||
* Open a transaction on a data type manager
|
||||
*
|
||||
* @param dataTypeManager the data type manager
|
||||
* @param description a description of the change
|
||||
* @param commitByDefault true to commit at the end of the block
|
||||
* @return the transaction handle
|
||||
*/
|
||||
public static UndoableTransaction start(DataTypeManager dataTypeManager, String description) {
|
||||
int tid = dataTypeManager.startTransaction(description);
|
||||
return new DataTypeManagerUndoableTransaction(dataTypeManager, tid, commitByDefault);
|
||||
return new DataTypeManagerUndoableTransaction(dataTypeManager, tid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a transaction on program user data
|
||||
*
|
||||
* @param userData the user data
|
||||
* @return the transaction handle
|
||||
*/
|
||||
public static UndoableTransaction start(ProgramUserData userData) {
|
||||
int tid = userData.startTransaction();
|
||||
return new ProgramUserDataUndoableTransaction(userData, tid);
|
||||
}
|
||||
|
||||
abstract class AbstractUndoableTransaction implements UndoableTransaction {
|
||||
protected final int transactionID;
|
||||
|
||||
private boolean commit;
|
||||
private boolean commit = true;
|
||||
private boolean open = true;
|
||||
|
||||
private AbstractUndoableTransaction(int transactionID, boolean commitByDefault) {
|
||||
this.transactionID = transactionID;
|
||||
this.commit = commitByDefault;
|
||||
protected AbstractUndoableTransaction() {
|
||||
}
|
||||
|
||||
abstract void endTransaction(@SuppressWarnings("hiding") boolean commit);
|
||||
|
||||
@Override
|
||||
public void abortOnClose() {
|
||||
commit = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitOnClose() {
|
||||
commit = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
if (open) {
|
||||
@@ -85,12 +166,54 @@ public interface UndoableTransaction extends AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
class DomainObjectUndoableTransaction extends AbstractUndoableTransaction {
|
||||
abstract class AbstractLongUndoableTransaction extends AbstractUndoableTransaction {
|
||||
final long transactionID;
|
||||
|
||||
public AbstractLongUndoableTransaction(long transactionID) {
|
||||
super();
|
||||
this.transactionID = transactionID;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractIntUndoableTransaction extends AbstractUndoableTransaction {
|
||||
final int transactionID;
|
||||
|
||||
public AbstractIntUndoableTransaction(int transactionID) {
|
||||
super();
|
||||
this.transactionID = transactionID;
|
||||
}
|
||||
}
|
||||
|
||||
class DBHandleUndoableTransaction extends AbstractLongUndoableTransaction {
|
||||
private final DBHandle handle;
|
||||
private final ErrorHandler errHandler;
|
||||
|
||||
public DBHandleUndoableTransaction(DBHandle handle, long transactionID,
|
||||
ErrorHandler errHandler) {
|
||||
super(transactionID);
|
||||
this.handle = handle;
|
||||
this.errHandler = errHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
void endTransaction(boolean commit) {
|
||||
if (!commit) {
|
||||
Msg.debug(this, "Aborting transaction");
|
||||
}
|
||||
try {
|
||||
handle.endTransaction(transactionID, commit);
|
||||
}
|
||||
catch (IOException e) {
|
||||
errHandler.dbError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DomainObjectUndoableTransaction extends AbstractIntUndoableTransaction {
|
||||
private final UndoableDomainObject domainObject;
|
||||
|
||||
private DomainObjectUndoableTransaction(UndoableDomainObject domainObject, int tid,
|
||||
boolean commitByDefault) {
|
||||
super(tid, commitByDefault);
|
||||
private DomainObjectUndoableTransaction(UndoableDomainObject domainObject, int tid) {
|
||||
super(tid);
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
@@ -103,12 +226,11 @@ public interface UndoableTransaction extends AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
class DataTypeManagerUndoableTransaction extends AbstractUndoableTransaction {
|
||||
class DataTypeManagerUndoableTransaction extends AbstractIntUndoableTransaction {
|
||||
private final DataTypeManager dataTypeManager;
|
||||
|
||||
private DataTypeManagerUndoableTransaction(DataTypeManager dataTypeManager, int tid,
|
||||
boolean commitByDefault) {
|
||||
super(tid, commitByDefault);
|
||||
private DataTypeManagerUndoableTransaction(DataTypeManager dataTypeManager, int tid) {
|
||||
super(tid);
|
||||
this.dataTypeManager = dataTypeManager;
|
||||
}
|
||||
|
||||
@@ -118,14 +240,19 @@ public interface UndoableTransaction extends AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
class ProgramUserDataUndoableTransaction extends AbstractUndoableTransaction {
|
||||
class ProgramUserDataUndoableTransaction extends AbstractIntUndoableTransaction {
|
||||
private final ProgramUserData userData;
|
||||
|
||||
private ProgramUserDataUndoableTransaction(ProgramUserData userData, int tid) {
|
||||
super(tid, true);
|
||||
super(tid);
|
||||
this.userData = userData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abortOnClose() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort() {
|
||||
throw new UnsupportedOperationException();
|
||||
@@ -137,8 +264,48 @@ public interface UndoableTransaction extends AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this transaction to commit when closed
|
||||
*
|
||||
* <p>
|
||||
* This is the default behavior. If an error occurs, or when the end of the try block is
|
||||
* reached, the transaction will be committed. The user is expected to undo unwanted
|
||||
* transactions, including those committed with an error. It could be the results are still
|
||||
* mostly correct. Additionally, aborting a transaction can roll back other concurrent
|
||||
* transactions.
|
||||
*/
|
||||
void commitOnClose();
|
||||
|
||||
/**
|
||||
* Set this transaction to abort by when closed
|
||||
*
|
||||
* <p>
|
||||
* Ordinarily, if an error occurs, the transaction is committed as is. The user is expected to
|
||||
* undo unwanted transactions. Calling this method will cause the transaction to be aborted
|
||||
* instead. <b>WARNING:</b> Aborting this transaction may abort other concurrent transactions.
|
||||
* Use with extreme care. <b>NOTE:</b> Use of this method requires that the transaction be
|
||||
* explicitly committed using {@link #commit()}. When this transaction is closed, if it hasn't
|
||||
* been committed, it will be aborted.
|
||||
*/
|
||||
void abortOnClose();
|
||||
|
||||
/**
|
||||
* Commit the transaction and close it immediately
|
||||
*
|
||||
* <p>
|
||||
* Note that attempting to make changes after this call will likely result in a
|
||||
* {@link NoTransactionException}.
|
||||
*/
|
||||
void commit();
|
||||
|
||||
/**
|
||||
* Abort the transaction and close it immediately
|
||||
*
|
||||
* <p>
|
||||
* Note that attempting to make changes after this call will likely result in a
|
||||
* {@link NoTransactionException}. <b>WARNING:</b> Aborting this transaction may abort other
|
||||
* concurrent transactions. Use with extreme care.
|
||||
*/
|
||||
void abort();
|
||||
|
||||
@Override
|
||||
|
||||
+14
@@ -17,8 +17,22 @@ package ghidra.util.database.annot;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import ghidra.util.database.DBAnnotatedObject;
|
||||
import ghidra.util.database.DBObjectColumn;
|
||||
|
||||
/**
|
||||
* Mark a {@link DBObjectColumn} to receive a column handle
|
||||
*
|
||||
* @see DBAnnotatedObject
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DBAnnotatedColumn {
|
||||
/**
|
||||
* The name of the column
|
||||
*
|
||||
* <p>
|
||||
* There should be a {@link DBAnnotatedField} annotation with the same column name
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
||||
+39
@@ -17,19 +17,58 @@ package ghidra.util.database.annot;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import db.Field;
|
||||
import ghidra.util.database.DBAnnotatedObject;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.DBFieldCodec;
|
||||
|
||||
/**
|
||||
* Mark a field to be stored in a table column
|
||||
*
|
||||
* @see DBAnnotatedObject
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DBAnnotatedField {
|
||||
/**
|
||||
* The name of the column
|
||||
*
|
||||
* <p>
|
||||
* There should be a {@link DBAnnotatedColumn} annotation with the same column name
|
||||
*/
|
||||
String column();
|
||||
|
||||
/**
|
||||
* True to index the column
|
||||
*/
|
||||
boolean indexed() default false;
|
||||
|
||||
/**
|
||||
* True to use sparse storage
|
||||
*
|
||||
* <p>
|
||||
* If the {@link Field} used by the codec does not support null values, this can be set to true
|
||||
* to allow null values.
|
||||
*/
|
||||
boolean sparse() default false;
|
||||
|
||||
/**
|
||||
* Specify a custom codec
|
||||
*
|
||||
* <p>
|
||||
* This is not required for types supported directly by a {@link Field}.
|
||||
*
|
||||
* @see DBFieldCodec
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends DBFieldCodec> codec() default DefaultCodec.class;
|
||||
|
||||
/**
|
||||
* A placeholder class
|
||||
*
|
||||
* <p>
|
||||
* A reference to this class type indicates that {@link DBAnnotatedField#codec()} was not set.
|
||||
* The framework will instead check for a built-in codec.
|
||||
*/
|
||||
static abstract class DefaultCodec<OT extends DBAnnotatedObject, FT extends db.Field>
|
||||
implements DBFieldCodec<Void, OT, FT> {
|
||||
private DefaultCodec() {
|
||||
|
||||
-25
@@ -1,25 +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.annot;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
// TODO: Process these and specify or build indices accordingly.
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DBAnnotatedIndex {
|
||||
// None
|
||||
}
|
||||
+22
@@ -17,8 +17,30 @@ package ghidra.util.database.annot;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
import ghidra.util.database.DBAnnotatedObject;
|
||||
|
||||
/**
|
||||
* Required annotation for {@link DBAnnotatedObject}
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DBAnnotatedObjectInfo {
|
||||
/**
|
||||
* The schema version
|
||||
*
|
||||
* <p>
|
||||
* This should be incremented in many situtations, including but not limited to:
|
||||
* <ul>
|
||||
* <li>A field is added or removed</li>
|
||||
* <li>A field's type changes</li>
|
||||
* <li>A field's column name changes. See {@link DBAnnotatedField#column()}</li>
|
||||
* <li>A field's codec changes. See {@link DBAnnotatedField#codec()}</li>
|
||||
* <li>A field's sparse-storage flag changes. See {@link DBAnnotatedField#sparse()}</li>
|
||||
* <li>A field's index flag changes. See {@link DBAnnotatedField#indexed()}</li>
|
||||
* <li>The order of field declarations changes.</li>
|
||||
* <li>The codec used by a field changes how it encodes values</li>
|
||||
* <li>The fields of a superclass change in any of the above ways</li>
|
||||
* </ul>
|
||||
*/
|
||||
int version();
|
||||
}
|
||||
|
||||
+3
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package ghidra.util.database.err;
|
||||
|
||||
/**
|
||||
* Exception when a custom codec is required
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class NoDefaultCodecException extends RuntimeException {
|
||||
public NoDefaultCodecException() {
|
||||
|
||||
+7
-7
@@ -114,7 +114,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
|
||||
start = space.getAddress(0x00400000);
|
||||
size = 0x1000;
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
block = program.getMemory()
|
||||
.createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY,
|
||||
false);
|
||||
@@ -194,7 +194,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
|
||||
|
||||
@Test
|
||||
public void testWriteStdout() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV RAX," + SYSCALLNO_WRITE,
|
||||
"MOV RDI," + EmuUnixFileDescriptor.FD_STDOUT,
|
||||
@@ -226,7 +226,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
|
||||
|
||||
@Test
|
||||
public void testReadStdin() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV RAX," + SYSCALLNO_READ,
|
||||
"MOV RDI," + EmuUnixFileDescriptor.FD_STDIN,
|
||||
@@ -258,7 +258,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
|
||||
|
||||
@Test
|
||||
public void testWritevStdout() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
Address data = space.getAddress(0x00400800);
|
||||
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
@@ -313,7 +313,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
|
||||
public void testReadvStdin() throws Exception {
|
||||
Address strHello;
|
||||
Address strWorld;
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
Address data = space.getAddress(0x00400800);
|
||||
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
@@ -369,7 +369,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
|
||||
|
||||
@Test
|
||||
public void testOpenWriteClose() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV RAX," + SYSCALLNO_OPEN,
|
||||
"LEA RDI,[0x00400880]",
|
||||
@@ -407,7 +407,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles
|
||||
|
||||
@Test
|
||||
public void testOpenReadClose() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV RAX," + SYSCALLNO_OPEN,
|
||||
"LEA RDI,[0x00400880]",
|
||||
|
||||
+7
-7
@@ -114,7 +114,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
|
||||
start = space.getAddress(0x00400000);
|
||||
size = 0x1000;
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
block = program.getMemory()
|
||||
.createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY,
|
||||
false);
|
||||
@@ -194,7 +194,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
|
||||
|
||||
@Test
|
||||
public void testWriteStdout() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV EAX," + SYSCALLNO_WRITE,
|
||||
"MOV EBX," + EmuUnixFileDescriptor.FD_STDOUT,
|
||||
@@ -226,7 +226,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
|
||||
|
||||
@Test
|
||||
public void testReadStdin() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV EAX," + SYSCALLNO_READ,
|
||||
"MOV EBX," + EmuUnixFileDescriptor.FD_STDIN,
|
||||
@@ -258,7 +258,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
|
||||
|
||||
@Test
|
||||
public void testWritevStdout() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
Address data = space.getAddress(0x00400800);
|
||||
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
@@ -313,7 +313,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
|
||||
public void testReadvStdin() throws Exception {
|
||||
Address strHello;
|
||||
Address strWorld;
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
Address data = space.getAddress(0x00400800);
|
||||
ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
@@ -369,7 +369,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
|
||||
|
||||
@Test
|
||||
public void testOpenWriteClose() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV EAX," + SYSCALLNO_OPEN,
|
||||
"LEA EBX,[0x00400880]",
|
||||
@@ -407,7 +407,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI
|
||||
|
||||
@Test
|
||||
public void testOpenReadClose() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV EAX," + SYSCALLNO_OPEN,
|
||||
"LEA EBX,[0x00400880]",
|
||||
|
||||
+3
-3
@@ -128,7 +128,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
|
||||
start = space.getAddress(0x00400000);
|
||||
size = 0x1000;
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
block = program.getMemory()
|
||||
.createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY,
|
||||
false);
|
||||
@@ -177,7 +177,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
|
||||
|
||||
@Test
|
||||
public void testSyscallWithStdcallConvention() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV RAX,0",
|
||||
"MOV RCX,0xbeef",
|
||||
@@ -196,7 +196,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte
|
||||
|
||||
@Test
|
||||
public void testSyscallWithSyscallConvention() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
|
||||
asm.assemble(start,
|
||||
"MOV RAX,1",
|
||||
"MOV RCX,0xdead",
|
||||
|
||||
+4
-4
@@ -671,7 +671,7 @@ public class DBCachedObjectStoreFactoryTest {
|
||||
DBHandle handle = new DBHandle();
|
||||
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
|
||||
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
|
||||
factory.getOrCreateCachedStore(MyObject.TABLE_NAME, MyObject.class, MyObject::new,
|
||||
false);
|
||||
factory.getOrCreateCachedStore(MyExtObject.TABLE_NAME, MyExtObject.class,
|
||||
@@ -715,7 +715,7 @@ public class DBCachedObjectStoreFactoryTest {
|
||||
DBHandle handle = new DBHandle();
|
||||
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
|
||||
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
|
||||
factory.getOrCreateCachedStore("MyTable", MyFromAbstract.class, MyFromAbstract::new,
|
||||
false);
|
||||
}
|
||||
@@ -734,7 +734,7 @@ public class DBCachedObjectStoreFactoryTest {
|
||||
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
|
||||
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
|
||||
DBCachedObjectStore<MyKitchenSink> store;
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
|
||||
store = factory.getOrCreateCachedStore(MyKitchenSink.TABLE_NAME, MyKitchenSink.class,
|
||||
MyKitchenSink::new, false);
|
||||
|
||||
@@ -779,7 +779,7 @@ public class DBCachedObjectStoreFactoryTest {
|
||||
DBHandle handle = new DBHandle();
|
||||
MyDomainObject myDO = new MyDomainObject(handle, "Testing", this);
|
||||
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(myDO);
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables", true)) {
|
||||
try (UndoableTransaction trans = UndoableTransaction.start(myDO, "Create Tables")) {
|
||||
factory.getOrCreateCachedStore(MyUsesMyEnumTooBig.TABLE_NAME, MyUsesMyEnumTooBig.class,
|
||||
MyUsesMyEnumTooBig::new, false);
|
||||
fail();
|
||||
|
||||
+15
-15
@@ -72,7 +72,7 @@ public class DBCachedObjectStoreTest {
|
||||
super(new DBHandle(), DBOpenMode.CREATE, new ConsoleTaskMonitor(), name, timeInterval,
|
||||
bufSize, consumer);
|
||||
this.storeFactory = new DBCachedObjectStoreFactory(this);
|
||||
try (DBTransaction tid = DBTransaction.start(dbh, true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(dbh, this)) {
|
||||
this.store = storeFactory.getOrCreateCachedStore(OBJECTS_TABLE_NAME, MyObject.class,
|
||||
MyObject::new, false);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ public class DBCachedObjectStoreTest {
|
||||
throws VersionException, IOException {
|
||||
super(handle, openMode, monitor, null, timeInterval, bufSize, consumer);
|
||||
this.storeFactory = new DBCachedObjectStoreFactory(this);
|
||||
try (DBTransaction tid = DBTransaction.start(handle, true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(handle, this)) {
|
||||
this.store = storeFactory.getOrCreateCachedStore(OBJECTS_TABLE_NAME, MyObject.class,
|
||||
MyObject::new, false);
|
||||
}
|
||||
@@ -119,14 +119,14 @@ public class DBCachedObjectStoreTest {
|
||||
public void setF1(long f1) {
|
||||
if (this.f1 != f1) {
|
||||
this.f1 = f1;
|
||||
write(COL1);
|
||||
doWrite(COL1);
|
||||
}
|
||||
}
|
||||
|
||||
public void setF2(int f2) {
|
||||
if (this.f2 != f2) {
|
||||
this.f2 = f2;
|
||||
write(COL2);
|
||||
doWrite(COL2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public class DBCachedObjectStoreTest {
|
||||
DBCachedObjectStoreEntrySet<MyObject> rEntrySet;
|
||||
|
||||
protected UndoableTransaction trans() {
|
||||
return UndoableTransaction.start(myDomainObject, "Test", true);
|
||||
return UndoableTransaction.start(myDomainObject, "Test");
|
||||
}
|
||||
|
||||
protected void populateStore(long... keys) {
|
||||
@@ -270,7 +270,7 @@ public class DBCachedObjectStoreTest {
|
||||
MyObject obj = store.create();
|
||||
obj.setF1(0x801);
|
||||
obj.setF2(0x802);
|
||||
obj.updated();
|
||||
obj.doUpdated();
|
||||
Table table = handle.getTable(OBJECTS_TABLE_NAME);
|
||||
DBRecord record = table.getRecord(obj.getKey());
|
||||
assertEquals(0x801, record.getLongValue(0));
|
||||
@@ -2622,15 +2622,15 @@ public class DBCachedObjectStoreTest {
|
||||
try (UndoableTransaction tid = trans()) {
|
||||
MyObject obj0 = store.create(0);
|
||||
obj0.setF2(10);
|
||||
obj0.updated();
|
||||
obj0.doUpdated();
|
||||
|
||||
MyObject obj1 = store.create(1);
|
||||
obj1.setF2(5);
|
||||
obj1.updated();
|
||||
obj1.doUpdated();
|
||||
|
||||
MyObject obj2 = store.create(2);
|
||||
obj2.setF2(5);
|
||||
obj2.updated();
|
||||
obj2.doUpdated();
|
||||
}
|
||||
DBCachedObjectIndex<Integer, MyObject> index = store.getIndex(int.class, COL2_NAME);
|
||||
return index;
|
||||
@@ -2668,7 +2668,7 @@ public class DBCachedObjectStoreTest {
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
public void testFoundContains() throws IOException {
|
||||
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
|
||||
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
|
||||
Collection<MyObject> found5 = index.get(5);
|
||||
|
||||
assertFalse(found5.contains(null));
|
||||
assertFalse(found5.contains("Wrong type"));
|
||||
@@ -2679,7 +2679,7 @@ public class DBCachedObjectStoreTest {
|
||||
@Test
|
||||
public void testFoundIterator() throws IOException {
|
||||
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
|
||||
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
|
||||
Collection<MyObject> found5 = index.get(5);
|
||||
|
||||
assertEquals(Set.of(store.getObjectAt(1), store.getObjectAt(2)),
|
||||
new HashSet<>(IteratorUtils.toList(found5.iterator())));
|
||||
@@ -2688,7 +2688,7 @@ public class DBCachedObjectStoreTest {
|
||||
@Test
|
||||
public void testFoundToArray() throws IOException {
|
||||
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
|
||||
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
|
||||
Collection<MyObject> found5 = index.get(5);
|
||||
|
||||
assertEquals(Set.of(store.getObjectAt(1), store.getObjectAt(2)),
|
||||
new HashSet<>(Arrays.asList(found5.toArray())));
|
||||
@@ -2697,7 +2697,7 @@ public class DBCachedObjectStoreTest {
|
||||
@Test
|
||||
public void testFoundToTypedArray() throws IOException {
|
||||
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
|
||||
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
|
||||
Collection<MyObject> found5 = index.get(5);
|
||||
|
||||
assertEquals(Set.of(store.getObjectAt(1), store.getObjectAt(2)),
|
||||
new HashSet<>(Arrays.asList(found5.toArray(new MyObject[0]))));
|
||||
@@ -2712,7 +2712,7 @@ public class DBCachedObjectStoreTest {
|
||||
@Test
|
||||
public void testFoundContainsAll() throws VersionException, IOException {
|
||||
DBCachedObjectIndex<Integer, MyObject> index = populateAndGetIndex();
|
||||
DBCachedObjectStoreFoundKeysValueCollection<MyObject> found5 = index.get(5);
|
||||
Collection<MyObject> found5 = index.get(5);
|
||||
|
||||
assertTrue(found5.containsAll(List.of()));
|
||||
assertFalse(found5.containsAll(List.of(store.getObjectAt(1), "Wrong Type")));
|
||||
@@ -2722,7 +2722,7 @@ public class DBCachedObjectStoreTest {
|
||||
final MyObject altObj1;
|
||||
MyDomainObject altDomainObject = new MyDomainObject("Alternative Dummy", 500, 1000, this);
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(altDomainObject, "Create Obj2", true)) {
|
||||
UndoableTransaction.start(altDomainObject, "Create Obj2")) {
|
||||
altObj1 = altDomainObject.store.create(1);
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -636,7 +636,7 @@ public class RStarTreeMapTest {
|
||||
super(new DBHandle(), DBOpenMode.CREATE, new ConsoleTaskMonitor(), "Testing", 500, 1000,
|
||||
consumer);
|
||||
storeFactory = new DBCachedObjectStoreFactory(this);
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(this, "CreateMaps", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(this, "CreateMaps")) {
|
||||
tree = new IntRStarTree(storeFactory, DBIntRectStringDataRecord.TABLE_NAME,
|
||||
true, MAX_CHILDREN);
|
||||
map = tree.asSpatialMap();
|
||||
@@ -858,7 +858,7 @@ public class RStarTreeMapTest {
|
||||
List<Pair<IntRect, String>> entries = generateRandom(rect(0, 100, 0, 100), 10, 10, 125);
|
||||
obj.tree.checkIntegrity();
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom")) {
|
||||
for (Entry<IntRect, String> ent : entries) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
obj.tree.checkIntegrity();
|
||||
@@ -874,7 +874,7 @@ public class RStarTreeMapTest {
|
||||
// NOTE: This "thrashing" test covers nearly all the R*-Tree insertion logic.
|
||||
List<Pair<IntRect, String>> entries = generateRandom(rect(0, 100, 0, 100), 10, 10, 1000);
|
||||
Consumer<List<Pair<IntRect, String>>> inserter = list -> {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRandom")) {
|
||||
int i = 0;
|
||||
for (Entry<IntRect, String> ent : list) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
@@ -903,7 +903,7 @@ public class RStarTreeMapTest {
|
||||
|
||||
@Test
|
||||
public void testIntegrityWith2000VerticallyStackedRects() throws Exception {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddVertical", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddVertical")) {
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
obj.map.put(rect(0, 10, i, i + 1), "Ent" + i);
|
||||
// Note, underlying tree is not synchronized, but map is
|
||||
@@ -916,7 +916,7 @@ public class RStarTreeMapTest {
|
||||
|
||||
@Test
|
||||
public void testSaveAndLoad() throws IOException, CancelledException, VersionException {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRecord", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddRecord")) {
|
||||
obj.map.put(rect(1, 5, 6, 10), "Some value");
|
||||
}
|
||||
|
||||
@@ -958,7 +958,7 @@ public class RStarTreeMapTest {
|
||||
// NOTE: This test is made also to cover the visitation logic.
|
||||
assertTrue(obj.map.isEmpty());
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
|
||||
for (Entry<IntRect, String> ent : generatePoints(rect(1, 12, 1, 12))) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
@@ -977,7 +977,7 @@ public class RStarTreeMapTest {
|
||||
|
||||
@Test
|
||||
public void testFirst() {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
|
||||
for (Entry<IntRect, String> ent : generatePoints(rect(1, 12, 1, 12))) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
@@ -1015,7 +1015,7 @@ public class RStarTreeMapTest {
|
||||
@Test
|
||||
public void testIterator() {
|
||||
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
|
||||
for (Entry<IntRect, String> ent : points) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
@@ -1045,7 +1045,7 @@ public class RStarTreeMapTest {
|
||||
@Test
|
||||
public void testOrderedIterator() {
|
||||
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
|
||||
for (Entry<IntRect, String> ent : points) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
@@ -1073,13 +1073,13 @@ public class RStarTreeMapTest {
|
||||
public void testRemove() {
|
||||
// TODO: Add a "minimal query including" abstract method to reduce search for removed item
|
||||
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
|
||||
for (Entry<IntRect, String> ent : points) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints")) {
|
||||
assertFalse(obj.map.reduce(IntRectQuery.enclosed(rect(6, 6, 6, 6))).isEmpty());
|
||||
obj.map.remove(rect(6, 6, 6, 6), "NotHere");
|
||||
assertFalse(obj.map.reduce(IntRectQuery.enclosed(rect(6, 6, 6, 6))).isEmpty());
|
||||
@@ -1099,13 +1099,13 @@ public class RStarTreeMapTest {
|
||||
@Test
|
||||
public void testClear() {
|
||||
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
|
||||
for (Entry<IntRect, String> ent : points) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "RemovePoints")) {
|
||||
obj.map.reduce(IntRectQuery.enclosed(rect(6, 6, 6, 6))).clear();
|
||||
obj.tree.checkIntegrity();
|
||||
assertEquals(143, obj.map.size());
|
||||
@@ -1135,7 +1135,7 @@ public class RStarTreeMapTest {
|
||||
@Test
|
||||
public void testValuesToArray() {
|
||||
List<Pair<IntRect, String>> points = generatePoints(rect(1, 12, 1, 12));
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints", true)) {
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(obj, "AddPoints")) {
|
||||
for (Entry<IntRect, String> ent : points) {
|
||||
obj.map.put(ent.getKey(), ent.getValue());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user