mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 01:28:38 +08:00
GT-3294 Added support for DB FixedField with improved indexing.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,12 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Performs database consistency check on the current program
|
||||
import db.DBHandle;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import db.DBHandle;
|
||||
|
||||
public class ConsistencyCheck extends GhidraScript {
|
||||
|
||||
@@ -56,6 +54,8 @@ public class ConsistencyCheck extends GhidraScript {
|
||||
return;
|
||||
}
|
||||
|
||||
monitor.checkCanceled();
|
||||
|
||||
if (!df.canSave() || !currentProgram.hasExclusiveAccess()) {
|
||||
popup("Program database is NOT consistent!\nRebuild requires exclusive checkout.");
|
||||
return;
|
||||
@@ -67,19 +67,22 @@ public class ConsistencyCheck extends GhidraScript {
|
||||
}
|
||||
|
||||
end(false);
|
||||
|
||||
ProgramDB program = (ProgramDB) df.getDomainObject(this, false, false, monitor);
|
||||
|
||||
programMgr.closeProgram(currentProgram, true);
|
||||
|
||||
currentProgram = (Program) df.getDomainObject(this, false, false, monitor);
|
||||
dbh = ((ProgramDB) currentProgram).getDBHandle();
|
||||
monitor.clearCanceled(); // compensate for Script Manager cancelling task on program close
|
||||
|
||||
dbh = program.getDBHandle();
|
||||
try {
|
||||
boolean success = false;
|
||||
long txId = dbh.startTransaction();
|
||||
int txId = program.startTransaction("Rebuild DB Indexes");
|
||||
try {
|
||||
success = dbh.rebuild(monitor);
|
||||
}
|
||||
finally {
|
||||
dbh.endTransaction(txId, success);
|
||||
program.endTransaction(txId, success);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
@@ -92,11 +95,12 @@ public class ConsistencyCheck extends GhidraScript {
|
||||
return;
|
||||
}
|
||||
|
||||
currentProgram.save("DB Rebuild", monitor);
|
||||
program.save("DB Rebuild", monitor);
|
||||
}
|
||||
finally {
|
||||
currentProgram.release(this);
|
||||
currentProgram = programMgr.openProgram(df);
|
||||
programMgr.openProgram(program);
|
||||
program.release(this);
|
||||
currentProgram = program;
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
+17
-19
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,9 +15,6 @@
|
||||
*/
|
||||
package ghidra.app.plugin.debug.dbtable;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
@@ -26,6 +22,8 @@ import javax.swing.event.TableModelListener;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import db.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
public class DbLargeTableModel implements TableModel {
|
||||
private ArrayList<TableModelListener> listeners = new ArrayList<TableModelListener>();
|
||||
@@ -43,7 +41,7 @@ public class DbLargeTableModel implements TableModel {
|
||||
this.table = table;
|
||||
schema = table.getSchema();
|
||||
try {
|
||||
keyType = schema.getKeyFieldClass().newInstance();
|
||||
keyType = schema.getKeyFieldType();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
||||
@@ -59,39 +57,39 @@ public class DbLargeTableModel implements TableModel {
|
||||
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
columns.add(getColumn(schema.getKeyFieldClass()));
|
||||
columns.add(getColumn(schema.getKeyFieldType()));
|
||||
|
||||
Class<?>[] classes = schema.getFieldClasses();
|
||||
int fieldCount = schema.getFieldCount();
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
columns.add(getColumn(classes[i]));
|
||||
Field[] fields = schema.getFields();
|
||||
for (Field field : fields) {
|
||||
columns.add(getColumn(field));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AbstractColumnAdapter getColumn(Class<?> c) {
|
||||
if (c == ByteField.class) {
|
||||
private AbstractColumnAdapter getColumn(Field field) {
|
||||
if (field instanceof ByteField) {
|
||||
return new ByteColumnAdapter();
|
||||
}
|
||||
else if (c == BooleanField.class) {
|
||||
else if (field instanceof BooleanField) {
|
||||
return new BooleanColumnAdapter();
|
||||
}
|
||||
else if (c == ShortField.class) {
|
||||
else if (field instanceof ShortField) {
|
||||
return new ShortColumnAdapter();
|
||||
}
|
||||
else if (c == IntField.class) {
|
||||
else if (field instanceof IntField) {
|
||||
return new IntegerColumnAdapter();
|
||||
}
|
||||
else if (c == LongField.class) {
|
||||
else if (field instanceof LongField) {
|
||||
return new LongColumnAdapter();
|
||||
}
|
||||
else if (c == StringField.class) {
|
||||
else if (field instanceof StringField) {
|
||||
return new StringColumnAdapter();
|
||||
}
|
||||
else if (c == BinaryField.class) {
|
||||
else if (field instanceof BinaryField) {
|
||||
return new BinaryColumnAdapter();
|
||||
}
|
||||
throw new AssertException("New, unexpected DB column class type: " + c);
|
||||
throw new AssertException(
|
||||
"New, unexpected DB column type: " + field.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
private void findMinKey() throws IOException {
|
||||
|
||||
+14
-14
@@ -36,12 +36,11 @@ public class DbSmallTableModel extends AbstractSortedTableModel<Record> {
|
||||
|
||||
records = new ArrayList<>(table.getRecordCount());
|
||||
|
||||
columns.add(getColumn(schema.getKeyFieldClass()));
|
||||
columns.add(getColumn(schema.getKeyFieldType()));
|
||||
|
||||
Class<?>[] classes = schema.getFieldClasses();
|
||||
int fieldCount = schema.getFieldCount();
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
columns.add(getColumn(classes[i]));
|
||||
Field[] fields = schema.getFields();
|
||||
for (Field field : fields) {
|
||||
columns.add(getColumn(field));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -55,29 +54,30 @@ public class DbSmallTableModel extends AbstractSortedTableModel<Record> {
|
||||
}
|
||||
}
|
||||
|
||||
private AbstractColumnAdapter getColumn(Class<?> c) {
|
||||
if (c == ByteField.class) {
|
||||
private AbstractColumnAdapter getColumn(Field field) {
|
||||
if (field instanceof ByteField) {
|
||||
return new ByteColumnAdapter();
|
||||
}
|
||||
else if (c == BooleanField.class) {
|
||||
else if (field instanceof BooleanField) {
|
||||
return new BooleanColumnAdapter();
|
||||
}
|
||||
else if (c == ShortField.class) {
|
||||
else if (field instanceof ShortField) {
|
||||
return new ShortColumnAdapter();
|
||||
}
|
||||
else if (c == IntField.class) {
|
||||
else if (field instanceof IntField) {
|
||||
return new IntegerColumnAdapter();
|
||||
}
|
||||
else if (c == LongField.class) {
|
||||
else if (field instanceof LongField) {
|
||||
return new LongColumnAdapter();
|
||||
}
|
||||
else if (c == StringField.class) {
|
||||
else if (field instanceof StringField) {
|
||||
return new StringColumnAdapter();
|
||||
}
|
||||
else if (c == BinaryField.class) {
|
||||
else if (field instanceof BinaryField) {
|
||||
return new BinaryColumnAdapter();
|
||||
}
|
||||
throw new AssertException("New, unexpected DB column class type: " + c);
|
||||
throw new AssertException(
|
||||
"New, unexpected DB column type: " + field.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ public class AddressIndexPrimaryKeyIteratorTest extends AbstractGhidraHeadedInte
|
||||
|
||||
// Create table with indexed address column
|
||||
Schema schema =
|
||||
new Schema(0, "id", new Class[] { LongField.class }, new String[] { "addr" });
|
||||
new Schema(0, "id", new Field[] { LongField.INSTANCE }, new String[] { "addr" });
|
||||
DBHandle handle = program.getDBHandle();
|
||||
myTable = handle.createTable("MyTable", schema, new int[] { 0 });
|
||||
|
||||
|
||||
+17
-17
@@ -32,7 +32,7 @@ import ghidra.util.datastruct.LongArray;
|
||||
public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private static Schema SCHEMA =
|
||||
new Schema(0, "addr", new Class[] { StringField.class }, new String[] { "str" });
|
||||
new Schema(0, "addr", new Field[] { StringField.INSTANCE }, new String[] { "str" });
|
||||
|
||||
private ProgramDB program;
|
||||
private AddressSpace space;
|
||||
@@ -114,7 +114,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator0() throws Exception {
|
||||
public void testIterator0() throws Exception {
|
||||
AddressKeyIterator it = new AddressKeyIterator();
|
||||
assertTrue(!it.hasNext());
|
||||
assertTrue(!it.hasPrevious());
|
||||
@@ -133,7 +133,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator1() throws Exception {
|
||||
public void testIterator1() throws Exception {
|
||||
int index = 0;
|
||||
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, true);
|
||||
while (it.hasNext()) {
|
||||
@@ -144,7 +144,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator2() throws Exception {
|
||||
public void testIterator2() throws Exception {
|
||||
int index = 0x10;
|
||||
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x4000), true);
|
||||
while (it.hasNext()) {
|
||||
@@ -155,7 +155,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator3() throws Exception {
|
||||
public void testIterator3() throws Exception {
|
||||
int index = 0x11;
|
||||
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false);
|
||||
while (it.hasNext()) {
|
||||
@@ -166,7 +166,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator4() throws Exception {
|
||||
public void testIterator4() throws Exception {
|
||||
int index = 0x10;
|
||||
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true);
|
||||
while (it.hasNext()) {
|
||||
@@ -177,7 +177,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator5() throws Exception {
|
||||
public void testIterator5() throws Exception {
|
||||
int index = 0x0f;
|
||||
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), true);
|
||||
while (it.hasPrevious()) {
|
||||
@@ -188,7 +188,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator6() throws Exception {
|
||||
public void testIterator6() throws Exception {
|
||||
int index = 0x10;
|
||||
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, addr(0x5000), false);
|
||||
while (it.hasPrevious()) {
|
||||
@@ -199,7 +199,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator7() throws Exception {
|
||||
public void testIterator7() throws Exception {
|
||||
AddressSet set = new AddressSet();
|
||||
set.addRange(addr(0x3008), addr(0x5008));
|
||||
set.addRange(addr(0x9008), addr(0x10000));
|
||||
@@ -216,7 +216,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator8() throws Exception {
|
||||
public void testIterator8() throws Exception {
|
||||
AddressSet set = new AddressSet();
|
||||
set.addRange(addr(0x3008), addr(0x5008));
|
||||
set.addRange(addr(0x9008), addr(0x10000));
|
||||
@@ -233,7 +233,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator9() throws Exception {
|
||||
public void testIterator9() throws Exception {
|
||||
AddressSet set = new AddressSet();
|
||||
set.addRange(addr(0x3008), addr(0x5008));
|
||||
set.addRange(addr(0x9008), addr(0x10000));
|
||||
@@ -247,7 +247,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator10() throws Exception {
|
||||
public void testIterator10() throws Exception {
|
||||
AddressSet set = new AddressSet();
|
||||
set.addRange(addr(0x3008), addr(0x5008));
|
||||
set.addRange(addr(0x9008), addr(0x10000));
|
||||
@@ -264,7 +264,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator11() throws Exception {
|
||||
public void testIterator11() throws Exception {
|
||||
AddressSet set = new AddressSet();
|
||||
set.addRange(addr(0x3008), addr(0x5008));
|
||||
set.addRange(addr(0x9008), addr(0x10000));
|
||||
@@ -279,7 +279,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator12() throws Exception {
|
||||
public void testIterator12() throws Exception {
|
||||
int index = 0x3f;
|
||||
AddressKeyIterator it = new AddressKeyIterator(myTable, addrMap, null, false);
|
||||
while (it.hasPrevious()) {
|
||||
@@ -290,7 +290,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator13() throws Exception {
|
||||
public void testIterator13() throws Exception {
|
||||
AddressSet set = new AddressSet();
|
||||
set.addRange(addr(0x3008), addr(0x5008));
|
||||
set.addRange(addr(0x9008), addr(0x10000));
|
||||
@@ -304,7 +304,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIteratorCheckWrap1() throws Exception {
|
||||
public void testIteratorCheckWrap1() throws Exception {
|
||||
|
||||
addRecord(addr(0x0));
|
||||
addRecord(addr(0x0100));
|
||||
@@ -320,7 +320,7 @@ public class AddressKeyIteratorTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIteratorCheckWrap2() throws Exception {
|
||||
public void testIteratorCheckWrap2() throws Exception {
|
||||
|
||||
addRecord(addr(0x0));
|
||||
addRecord(addr(0x0100));
|
||||
|
||||
+7
-6
@@ -35,7 +35,8 @@ import ghidra.util.Lock;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
|
||||
public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest implements ErrorHandler {
|
||||
public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest
|
||||
implements ErrorHandler {
|
||||
|
||||
private TestEnv env; // needed to discover languages
|
||||
private ProgramDB program;
|
||||
@@ -83,7 +84,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
|
||||
public void testTransaction() {
|
||||
|
||||
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
|
||||
new Lock("Test"), "TEST", this, LongField.class, true);
|
||||
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
|
||||
|
||||
try {
|
||||
map.paintRange(addr(0), addr(0x1000), ONE);
|
||||
@@ -114,7 +115,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
|
||||
public void testPaint() {
|
||||
|
||||
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
|
||||
new Lock("Test"), "TEST", this, LongField.class, true);
|
||||
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
|
||||
|
||||
int id = program.startTransaction("TEST");
|
||||
try {
|
||||
@@ -152,7 +153,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
|
||||
public void testClear() {
|
||||
|
||||
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
|
||||
new Lock("Test"), "TEST", this, LongField.class, true);
|
||||
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
|
||||
|
||||
int id = program.startTransaction("TEST");
|
||||
try {
|
||||
@@ -186,7 +187,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
|
||||
public void testAddressRangeIterator() {
|
||||
|
||||
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
|
||||
new Lock("Test"), "TEST", this, LongField.class, true);
|
||||
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
|
||||
|
||||
int id = program.startTransaction("TEST");
|
||||
try {
|
||||
@@ -248,7 +249,7 @@ public class AddressRangeMapDBTest extends AbstractGhidraHeadedIntegrationTest i
|
||||
public void testMove() {
|
||||
|
||||
AddressRangeMapDB map = new AddressRangeMapDB(program.getDBHandle(), addrMap,
|
||||
new Lock("Test"), "TEST", this, LongField.class, true);
|
||||
new Lock("Test"), "TEST", this, LongField.INSTANCE, true);
|
||||
|
||||
int id = program.startTransaction("TEST");
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,57 +20,59 @@ import java.util.Random;
|
||||
|
||||
import db.*;
|
||||
|
||||
|
||||
|
||||
public class DatabaseBenchMarks {
|
||||
static int BUFFER_SIZE = 16*1024;
|
||||
static int CACHE_SIZE = 32*1024*1024;
|
||||
|
||||
static int BUFFER_SIZE = 16 * 1024;
|
||||
static int CACHE_SIZE = 32 * 1024 * 1024;
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestTimer timer = new TestTimer();
|
||||
|
||||
testOrderedIntInsertions(timer,1000);
|
||||
testOrderedIntInsertions(timer,10000);
|
||||
testOrderedIntInsertions(timer,100000);
|
||||
testOrderedIntInsertions(timer,1000000);
|
||||
|
||||
|
||||
testOrderedIntInsertions(timer, 1000);
|
||||
testOrderedIntInsertions(timer, 10000);
|
||||
testOrderedIntInsertions(timer, 100000);
|
||||
testOrderedIntInsertions(timer, 1000000);
|
||||
|
||||
System.out.println("");
|
||||
testOrderedStringInsertions(timer,1000);
|
||||
testOrderedStringInsertions(timer,10000);
|
||||
testOrderedStringInsertions(timer,100000);
|
||||
testOrderedStringInsertions(timer,1000000);
|
||||
|
||||
testOrderedStringInsertions(timer, 1000);
|
||||
testOrderedStringInsertions(timer, 10000);
|
||||
testOrderedStringInsertions(timer, 100000);
|
||||
testOrderedStringInsertions(timer, 1000000);
|
||||
|
||||
System.out.println("");
|
||||
testRandomIntInsertions(timer,1000);
|
||||
testRandomIntInsertions(timer,10000);
|
||||
testRandomIntInsertions(timer,100000);
|
||||
testRandomIntInsertions(timer,1000000);
|
||||
|
||||
testRandomIntInsertions(timer, 1000);
|
||||
testRandomIntInsertions(timer, 10000);
|
||||
testRandomIntInsertions(timer, 100000);
|
||||
testRandomIntInsertions(timer, 1000000);
|
||||
|
||||
System.out.println("");
|
||||
testIteration(timer);
|
||||
|
||||
|
||||
System.out.println("");
|
||||
testRandomAccess(timer);
|
||||
|
||||
}
|
||||
|
||||
private static void testOrderedIntInsertions(TestTimer timer, int numInsertions) {
|
||||
try {
|
||||
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
|
||||
long transactionID = dbh.startTransaction();
|
||||
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
|
||||
Schema schema =
|
||||
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
|
||||
Table table = dbh.createTable("Test", schema);
|
||||
Record record = schema.createRecord(0);
|
||||
timer.start("Inserting "+numInsertions+" sorted records with long keys and integer values");
|
||||
for(int i=0;i<numInsertions;i++) {
|
||||
timer.start(
|
||||
"Inserting " + numInsertions + " sorted records with long keys and integer values");
|
||||
for (int i = 0; i < numInsertions; i++) {
|
||||
record.setKey(i);
|
||||
record.setIntValue(0,i);
|
||||
record.setIntValue(0, i);
|
||||
table.putRecord(record);
|
||||
}
|
||||
timer.end();
|
||||
dbh.endTransaction(transactionID, true);
|
||||
dbh.close();
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,69 +80,77 @@ public class DatabaseBenchMarks {
|
||||
try {
|
||||
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
|
||||
long transactionID = dbh.startTransaction();
|
||||
Schema schema = new Schema(1, "Key", new Class[]{StringField.class}, new String[]{"Value"});
|
||||
Schema schema = new Schema(1, "Key", new Field[] { StringField.INSTANCE },
|
||||
new String[] { "Value" });
|
||||
Table table = dbh.createTable("Test", schema);
|
||||
Record record = schema.createRecord(0);
|
||||
timer.start("Inserting "+numInsertions+" sorted records with long keys and String (length = 8) values");
|
||||
for(int i=0;i<numInsertions;i++) {
|
||||
timer.start("Inserting " + numInsertions +
|
||||
" sorted records with long keys and String (length = 8) values");
|
||||
for (int i = 0; i < numInsertions; i++) {
|
||||
record.setKey(i);
|
||||
record.setString(0,"abcdefgh");
|
||||
record.setString(0, "abcdefgh");
|
||||
table.putRecord(record);
|
||||
}
|
||||
timer.end();
|
||||
dbh.endTransaction(transactionID, true);
|
||||
dbh.close();
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void testRandomIntInsertions(TestTimer timer, int numInsertions) {
|
||||
try {
|
||||
Random random = new Random();
|
||||
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
|
||||
long transactionID = dbh.startTransaction();
|
||||
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
|
||||
Schema schema =
|
||||
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
|
||||
Table table = dbh.createTable("Test", schema);
|
||||
Record record = schema.createRecord(0);
|
||||
timer.start("Inserting "+numInsertions+" random records with long keys and integer values");
|
||||
for(int i=0;i<numInsertions;i++) {
|
||||
timer.start(
|
||||
"Inserting " + numInsertions + " random records with long keys and integer values");
|
||||
for (int i = 0; i < numInsertions; i++) {
|
||||
record.setKey(random.nextLong());
|
||||
record.setIntValue(0,i);
|
||||
record.setIntValue(0, i);
|
||||
table.putRecord(record);
|
||||
}
|
||||
timer.end();
|
||||
dbh.endTransaction(transactionID, true);
|
||||
dbh.close();
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void testIteration(TestTimer timer) {
|
||||
try {
|
||||
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
|
||||
long transactionID = dbh.startTransaction();
|
||||
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
|
||||
Schema schema =
|
||||
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
|
||||
Table table = dbh.createTable("Test", schema);
|
||||
Record record = schema.createRecord(0);
|
||||
System.out.print("building database...");
|
||||
for(int i=0;i<1000000;i++) {
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
record.setKey(i);
|
||||
record.setIntValue(0,i);
|
||||
record.setIntValue(0, i);
|
||||
table.putRecord(record);
|
||||
}
|
||||
timer.start("Iterating over 1000000 int records");
|
||||
RecordIterator it = table.iterator();
|
||||
while(it.hasNext()) {
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
}
|
||||
timer.end();
|
||||
|
||||
timer.end();
|
||||
|
||||
dbh.endTransaction(transactionID, true);
|
||||
dbh.close();
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,42 +158,43 @@ public class DatabaseBenchMarks {
|
||||
try {
|
||||
DBHandle dbh = new DBHandle(BUFFER_SIZE, CACHE_SIZE);
|
||||
long transactionID = dbh.startTransaction();
|
||||
Schema schema = new Schema(1, "Key", new Class[]{IntField.class}, new String[]{"Value"});
|
||||
Schema schema =
|
||||
new Schema(1, "Key", new Field[] { IntField.INSTANCE }, new String[] { "Value" });
|
||||
Table table = dbh.createTable("Test", schema);
|
||||
Record record = schema.createRecord(0);
|
||||
System.out.print("building database...");
|
||||
for(int i=0;i<1000000;i++) {
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
record.setKey(i);
|
||||
record.setIntValue(0,i);
|
||||
record.setIntValue(0, i);
|
||||
table.putRecord(record);
|
||||
}
|
||||
Random random = new Random();
|
||||
timer.start("Randomly accessing 1000000 int records");
|
||||
for(int i=0;i<1000000;i++) {
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
table.getRecord(random.nextInt(1000000));
|
||||
}
|
||||
timer.end();
|
||||
|
||||
timer.end();
|
||||
|
||||
dbh.endTransaction(transactionID, true);
|
||||
dbh.close();
|
||||
}catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
class TestTimer {
|
||||
long start;
|
||||
|
||||
|
||||
void start(String testMsg) {
|
||||
System.out.print(testMsg+"... ");
|
||||
System.out.print(testMsg + "... ");
|
||||
start = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
void end() {
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println(""+(end-start)/1000.0+" seconds");
|
||||
System.out.println("" + (end - start) / 1000.0 + " seconds");
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+18
-17
@@ -44,11 +44,11 @@ public class FunctionsTable {
|
||||
static final int CACHE_SIZE = 10000;
|
||||
|
||||
// @formatter:off
|
||||
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Function ID", new Class[] {
|
||||
ShortField.class, LongField.class,
|
||||
ByteField.class, LongField.class, LongField.class,
|
||||
LongField.class, LongField.class, LongField.class,
|
||||
ByteField.class
|
||||
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Function ID", new Field[] {
|
||||
ShortField.INSTANCE, LongField.INSTANCE,
|
||||
ByteField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE,
|
||||
LongField.INSTANCE, LongField.INSTANCE, LongField.INSTANCE,
|
||||
ByteField.INSTANCE
|
||||
}, new String[] {
|
||||
"Code Unit Size", "Full Hash",
|
||||
"Specific Hash Additional Size", "Specific Hash", "Library ID",
|
||||
@@ -133,14 +133,15 @@ public class FunctionsTable {
|
||||
*/
|
||||
public List<FunctionRecord> getFunctionRecordsByFullHash(long hash) throws IOException {
|
||||
LongField hashField = new LongField(hash);
|
||||
DBLongIterator iterator = table.indexKeyIterator(FULL_HASH_COL, hashField, hashField, true);
|
||||
DBFieldIterator iterator =
|
||||
table.indexKeyIterator(FULL_HASH_COL, hashField, hashField, true);
|
||||
if (!iterator.hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<FunctionRecord> list = new ArrayList<>();
|
||||
while (iterator.hasNext()) {
|
||||
long key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key);
|
||||
Field key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
|
||||
if (functionRecord == null) {
|
||||
Record record = table.getRecord(key);
|
||||
functionRecord = new FunctionRecord(fidDb, functionCache, record);
|
||||
@@ -216,15 +217,15 @@ public class FunctionsTable {
|
||||
*/
|
||||
public List<FunctionRecord> getFunctionRecordsByNameSubstring(String nameSearch)
|
||||
throws IOException {
|
||||
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL);
|
||||
DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL);
|
||||
|
||||
if (!iterator.hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<FunctionRecord> list = new ArrayList<>();
|
||||
while (iterator.hasNext()) {
|
||||
long key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key);
|
||||
Field key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
|
||||
if (functionRecord == null) {
|
||||
Record record = table.getRecord(key);
|
||||
long nameID = record.getLongValue(NAME_ID_COL);
|
||||
@@ -255,15 +256,15 @@ public class FunctionsTable {
|
||||
*/
|
||||
public List<FunctionRecord> getFunctionRecordsByNameRegex(String regex) throws IOException {
|
||||
Matcher matcher = Pattern.compile(regex).matcher("");
|
||||
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL);
|
||||
DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL);
|
||||
|
||||
if (!iterator.hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<FunctionRecord> list = new ArrayList<>();
|
||||
while (iterator.hasNext()) {
|
||||
long key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key);
|
||||
Field key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
|
||||
if (functionRecord == null) {
|
||||
Record record = table.getRecord(key);
|
||||
long nameID = record.getLongValue(NAME_ID_COL);
|
||||
@@ -347,15 +348,15 @@ public class FunctionsTable {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LongField field = new LongField(stringID);
|
||||
DBLongIterator iterator = table.indexKeyIterator(NAME_ID_COL, field, field, true);
|
||||
DBFieldIterator iterator = table.indexKeyIterator(NAME_ID_COL, field, field, true);
|
||||
if (!iterator.hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final long libraryKey = library.getLibraryID();
|
||||
List<FunctionRecord> list = new ArrayList<>();
|
||||
while (iterator.hasNext()) {
|
||||
long key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key);
|
||||
Field key = iterator.next();
|
||||
FunctionRecord functionRecord = functionCache.get(key.getLongValue());
|
||||
if (functionRecord == null) {
|
||||
Record record = table.getRecord(key);
|
||||
if (record.getLongValue(LIBRARY_ID_COL) == libraryKey) {
|
||||
|
||||
@@ -49,10 +49,10 @@ public class LibrariesTable {
|
||||
static final int GHIDRA_COMPILER_SPEC_ID_COL = 7;
|
||||
|
||||
// @formatter:off
|
||||
static final Schema SCHEMA = new Schema(VERSION, "Library ID", new Class[] {
|
||||
StringField.class, StringField.class, StringField.class,
|
||||
StringField.class, StringField.class, IntField.class, IntField.class,
|
||||
StringField.class
|
||||
static final Schema SCHEMA = new Schema(VERSION, "Library ID", new Field[] {
|
||||
StringField.INSTANCE, StringField.INSTANCE, StringField.INSTANCE,
|
||||
StringField.INSTANCE, StringField.INSTANCE, IntField.INSTANCE, IntField.INSTANCE,
|
||||
StringField.INSTANCE
|
||||
}, new String[] {
|
||||
"Library Family Name", "Library Version", "Library Variant",
|
||||
"Ghidra Version", "Ghidra Language ID", "Ghidra Language Version", "Ghidra Language Minor Version",
|
||||
@@ -90,8 +90,9 @@ public class LibrariesTable {
|
||||
if (libraryVersion != VERSION) {
|
||||
String msg = "Expected version " + VERSION + " for table " + LIBRARIES_TABLE +
|
||||
" but got " + table.getSchema().getVersion();
|
||||
throw new VersionException(msg, libraryVersion < VERSION
|
||||
? VersionException.OLDER_VERSION : VersionException.NEWER_VERSION,
|
||||
throw new VersionException(msg,
|
||||
libraryVersion < VERSION ? VersionException.OLDER_VERSION
|
||||
: VersionException.NEWER_VERSION,
|
||||
false);
|
||||
}
|
||||
}
|
||||
@@ -155,14 +156,14 @@ public class LibrariesTable {
|
||||
public List<LibraryRecord> getLibrariesByName(String name, String version, String variant)
|
||||
throws IOException {
|
||||
StringField hashField = new StringField(name);
|
||||
DBLongIterator iterator =
|
||||
DBFieldIterator iterator =
|
||||
table.indexKeyIterator(LIBRARY_FAMILY_NAME_COL, hashField, hashField, true);
|
||||
if (!iterator.hasNext()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<LibraryRecord> list = new ArrayList<LibraryRecord>();
|
||||
while (iterator.hasNext()) {
|
||||
long key = iterator.next();
|
||||
Field key = iterator.next();
|
||||
Record record = table.getRecord(key);
|
||||
LibraryRecord libraryRecord = new LibraryRecord(record);
|
||||
if (version != null) {
|
||||
|
||||
@@ -27,8 +27,8 @@ public class RelationsTable {
|
||||
// static final int CACHE_SIZE = 10000;
|
||||
|
||||
// @formatter:off
|
||||
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Relation Smash", new Class[] {
|
||||
}, new String[] {
|
||||
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "Relation Smash",
|
||||
new Field[] { }, new String[] {
|
||||
});
|
||||
// @formatter:on
|
||||
|
||||
|
||||
@@ -35,11 +35,9 @@ public class StringsTable {
|
||||
static final int CACHE_SIZE = 10000;
|
||||
|
||||
// @formatter:off
|
||||
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "String ID", new Class[] {
|
||||
StringField.class
|
||||
}, new String[] {
|
||||
"String Value"
|
||||
});
|
||||
static final Schema SCHEMA = new Schema(LibrariesTable.VERSION, "String ID",
|
||||
new Field[] { StringField.INSTANCE },
|
||||
new String[] { "String Value" });
|
||||
// @formatter:on
|
||||
|
||||
static int[] INDEXED_COLUMNS = new int[] { STRING_VALUE_COL };
|
||||
@@ -69,29 +67,30 @@ public class StringsTable {
|
||||
* @throws IOException if the database has a problem
|
||||
*/
|
||||
long obtainStringID(String value) throws IOException {
|
||||
long[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
|
||||
Field[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
|
||||
if (records == null || records.length == 0) {
|
||||
// create
|
||||
Record record = SCHEMA.createRecord(UniversalIdGenerator.nextID().getValue());
|
||||
long key = UniversalIdGenerator.nextID().getValue();
|
||||
Record record = SCHEMA.createRecord(key);
|
||||
record.setString(STRING_VALUE_COL, value);
|
||||
table.putRecord(record);
|
||||
return record.getKey();
|
||||
return key;
|
||||
}
|
||||
return records[0];
|
||||
return records[0].getLongValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup existing ID or return null for String value.
|
||||
* @param value the string value
|
||||
* @return the existing interned string primary key, or null if nonexistent
|
||||
* @return the existing interned string primary key as LongField, or null if nonexistent
|
||||
* @throws IOException if the database has a problem
|
||||
*/
|
||||
Long lookupStringID(String value) throws IOException {
|
||||
long[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
|
||||
Field[] records = table.findRecords(new StringField(value), STRING_VALUE_COL);
|
||||
if (records == null || records.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return records[0];
|
||||
return records[0].getLongValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+18
-19
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,41 +13,41 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package db.util;
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import db.Field;
|
||||
|
||||
public class TableColumn {
|
||||
|
||||
private final Class<? extends Field> columnClass;
|
||||
private final Field columnField;
|
||||
private boolean indexed;
|
||||
|
||||
|
||||
private int ordinal;
|
||||
private String name;
|
||||
|
||||
public TableColumn( Class<? extends Field> columnClass ) {
|
||||
this( columnClass, false );
|
||||
|
||||
public TableColumn(Field columnField) {
|
||||
this(columnField, false);
|
||||
}
|
||||
|
||||
public TableColumn( Class<? extends Field> columnClass, boolean isIndexed ) {
|
||||
this.columnClass = columnClass;
|
||||
|
||||
public TableColumn(Field columnField, boolean isIndexed) {
|
||||
this.columnField = columnField;
|
||||
indexed = isIndexed;
|
||||
}
|
||||
|
||||
void setName( String name ) {
|
||||
this.name = name;
|
||||
void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
void setOrdinal( int ordinal ) {
|
||||
|
||||
void setOrdinal(int ordinal) {
|
||||
this.ordinal = ordinal;
|
||||
}
|
||||
|
||||
|
||||
public boolean isIndexed() {
|
||||
return indexed;
|
||||
}
|
||||
|
||||
public Class<? extends Field> getColumnClass() {
|
||||
return columnClass;
|
||||
|
||||
public Field getColumnField() {
|
||||
return columnField;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
@@ -61,6 +60,6 @@ public class TableColumn {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name() + "("+ ordinal +")";
|
||||
return name() + "(" + ordinal + ")";
|
||||
}
|
||||
}
|
||||
+29
-33
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,63 +13,61 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package db.util;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import db.Field;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class TableDescriptor {
|
||||
|
||||
private TableColumn[] columns;
|
||||
|
||||
|
||||
protected TableDescriptor() {
|
||||
this.columns = discoverTableColumns();
|
||||
}
|
||||
|
||||
private TableColumn[] discoverTableColumns() {
|
||||
|
||||
|
||||
|
||||
Class<? extends TableDescriptor> clazz = getClass();
|
||||
java.lang.reflect.Field[] fields = clazz.getFields();
|
||||
List<TableColumn> list = new ArrayList<TableColumn>(fields.length);
|
||||
for ( java.lang.reflect.Field field : fields ) {
|
||||
for (java.lang.reflect.Field field : fields) {
|
||||
Class<?> type = field.getType();
|
||||
if ( !TableColumn.class.isAssignableFrom( type ) ) {
|
||||
if (!TableColumn.class.isAssignableFrom(type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
TableColumn column = (TableColumn) field.get( null );
|
||||
column.setName( field.getName() );
|
||||
column.setOrdinal( list.size() );
|
||||
list.add( column );
|
||||
TableColumn column = (TableColumn) field.get(null);
|
||||
column.setName(field.getName());
|
||||
column.setOrdinal(list.size());
|
||||
list.add(column);
|
||||
}
|
||||
catch ( IllegalArgumentException e ) {
|
||||
catch (IllegalArgumentException e) {
|
||||
// shouldn't happen
|
||||
}
|
||||
catch ( IllegalAccessException e ) {
|
||||
Msg.showError( this, null, "Class Usage Error", "You must provide public " +
|
||||
"static members for your TableColumns" );
|
||||
catch (IllegalAccessException e) {
|
||||
Msg.showError(this, null, "Class Usage Error",
|
||||
"You must provide public " + "static members for your TableColumns");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return list.toArray( new TableColumn[list.size()] );
|
||||
|
||||
return list.toArray(new TableColumn[list.size()]);
|
||||
}
|
||||
|
||||
public int[] getIndexedColumns() {
|
||||
int count = 0;
|
||||
for ( TableColumn column : columns ) {
|
||||
for (TableColumn column : columns) {
|
||||
if (column.isIndexed()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
int[] indexedColumns = new int[count];
|
||||
count = 0;
|
||||
for ( TableColumn column : columns ) {
|
||||
for (TableColumn column : columns) {
|
||||
if (column.isIndexed()) {
|
||||
indexedColumns[count++] = column.column();
|
||||
}
|
||||
@@ -80,19 +77,18 @@ public class TableDescriptor {
|
||||
|
||||
public String[] getColumnNames() {
|
||||
List<String> list = new LinkedList<String>();
|
||||
for ( TableColumn column : columns ) {
|
||||
list.add( column.name() );
|
||||
for (TableColumn column : columns) {
|
||||
list.add(column.name());
|
||||
}
|
||||
return list.toArray( new String[ columns.length ] );
|
||||
return list.toArray(new String[columns.length]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // we know our class types are safe
|
||||
public Class<? extends Field>[] getColumnClasses() {
|
||||
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
|
||||
for ( TableColumn column : columns ) {
|
||||
list.add( column.getColumnClass() );
|
||||
}
|
||||
return list.toArray( new Class[ columns.length ] );
|
||||
public Field[] getColumnFields() {
|
||||
Field[] fields = new Field[columns.length];
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
fields[i] = columns[i].getColumnField().newField();
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
}
|
||||
+21
-19
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,48 +16,50 @@
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import static ghidra.feature.vt.api.db.VTAddressCorrelatorAdapter.AddressCorrelationTableDescriptor.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import db.*;
|
||||
import db.util.TableColumn;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class VTAddressCorrelatorAdapter {
|
||||
|
||||
public static class AddressCorrelationTableDescriptor extends db.util.TableDescriptor {
|
||||
public static class AddressCorrelationTableDescriptor
|
||||
extends ghidra.feature.vt.api.db.TableDescriptor {
|
||||
|
||||
public static TableColumn SOURCE_ENTRY_COL = new TableColumn(LongField.class, true);
|
||||
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class);
|
||||
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class);
|
||||
|
||||
public static AddressCorrelationTableDescriptor INSTANCE = new AddressCorrelationTableDescriptor();
|
||||
public static TableColumn SOURCE_ENTRY_COL = new TableColumn(LongField.INSTANCE, true);
|
||||
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
|
||||
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
|
||||
|
||||
public static AddressCorrelationTableDescriptor INSTANCE =
|
||||
new AddressCorrelationTableDescriptor();
|
||||
}
|
||||
|
||||
|
||||
static String TABLE_NAME = "AddressCorrelationTable";
|
||||
static Schema TABLE_SCHEMA = new Schema(0, "Key",
|
||||
INSTANCE.getColumnClasses(), INSTANCE.getColumnNames());
|
||||
static Schema TABLE_SCHEMA =
|
||||
new Schema(0, "Key", INSTANCE.getColumnFields(), INSTANCE.getColumnNames());
|
||||
static int[] TABLE_INDEXES = INSTANCE.getIndexedColumns();
|
||||
private DBHandle dbHandle;
|
||||
|
||||
|
||||
protected VTAddressCorrelatorAdapter(DBHandle dbHandle) {
|
||||
this.dbHandle = dbHandle;
|
||||
}
|
||||
|
||||
|
||||
public static VTAddressCorrelatorAdapter createAdapter(DBHandle dbHandle) throws IOException {
|
||||
return new VTAddressCorrelationAdapterV0(dbHandle);
|
||||
}
|
||||
|
||||
public static VTAddressCorrelatorAdapter getAdapter(DBHandle dbHandle, TaskMonitor monitor)
|
||||
public static VTAddressCorrelatorAdapter getAdapter(DBHandle dbHandle, TaskMonitor monitor)
|
||||
throws VersionException {
|
||||
return new VTAddressCorrelationAdapterV0(dbHandle, monitor);
|
||||
}
|
||||
|
||||
abstract void createAddressRecord(long sourceEntryLong, long sourceLong, long destinationLong) throws IOException;
|
||||
abstract void createAddressRecord(long sourceEntryLong, long sourceLong, long destinationLong)
|
||||
throws IOException;
|
||||
|
||||
abstract List<Record> getAddressRecords(long sourceEntryLong) throws IOException;
|
||||
|
||||
@@ -69,8 +70,9 @@ public abstract class VTAddressCorrelatorAdapter {
|
||||
void save(TaskMonitor monitor) throws CancelledException, IOException {
|
||||
dbHandle.save("", null, monitor);
|
||||
}
|
||||
|
||||
void saveAs(File file, TaskMonitor monitor) throws CancelledException, IOException {
|
||||
dbHandle.saveAs(file, true, monitor);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+15
-15
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,35 +15,36 @@
|
||||
*/
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import static ghidra.feature.vt.api.db.VTAssociationTableDBAdapter.AssociationTableDescriptor.INSTANCE;
|
||||
import ghidra.feature.vt.api.main.VTAssociationStatus;
|
||||
import ghidra.feature.vt.api.main.VTAssociationType;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import static ghidra.feature.vt.api.db.VTAssociationTableDBAdapter.AssociationTableDescriptor.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import db.*;
|
||||
import db.util.TableColumn;
|
||||
import ghidra.feature.vt.api.main.VTAssociationStatus;
|
||||
import ghidra.feature.vt.api.main.VTAssociationType;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class VTAssociationTableDBAdapter {
|
||||
|
||||
public static class AssociationTableDescriptor extends db.util.TableDescriptor {
|
||||
public static class AssociationTableDescriptor
|
||||
extends ghidra.feature.vt.api.db.TableDescriptor {
|
||||
|
||||
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class, true);
|
||||
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class, true);
|
||||
public static TableColumn TYPE_COL = new TableColumn(ByteField.class);
|
||||
public static TableColumn STATUS_COL = new TableColumn(ByteField.class);
|
||||
public static TableColumn APPLIED_STATUS_COL = new TableColumn(ByteField.class);
|
||||
public static TableColumn VOTE_COUNT_COL = new TableColumn(IntField.class);
|
||||
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE, true);
|
||||
public static TableColumn DESTINATION_ADDRESS_COL =
|
||||
new TableColumn(LongField.INSTANCE, true);
|
||||
public static TableColumn TYPE_COL = new TableColumn(ByteField.INSTANCE);
|
||||
public static TableColumn STATUS_COL = new TableColumn(ByteField.INSTANCE);
|
||||
public static TableColumn APPLIED_STATUS_COL = new TableColumn(ByteField.INSTANCE);
|
||||
public static TableColumn VOTE_COUNT_COL = new TableColumn(IntField.INSTANCE);
|
||||
|
||||
public static AssociationTableDescriptor INSTANCE = new AssociationTableDescriptor();
|
||||
}
|
||||
|
||||
static String TABLE_NAME = "AssociationTable";
|
||||
static Schema TABLE_SCHEMA =
|
||||
new Schema(0, "Key", INSTANCE.getColumnClasses(), INSTANCE.getColumnNames());
|
||||
new Schema(0, "Key", INSTANCE.getColumnFields(), INSTANCE.getColumnNames());
|
||||
static int[] TABLE_INDEXES = INSTANCE.getIndexedColumns();
|
||||
|
||||
public static VTAssociationTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
|
||||
|
||||
+16
-19
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,36 +15,35 @@
|
||||
*/
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.INSTANCE;
|
||||
import ghidra.feature.vt.api.impl.MarkupItemStorage;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import static ghidra.feature.vt.api.db.VTMatchMarkupItemTableDBAdapter.MarkupTableDescriptor.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.*;
|
||||
import db.util.TableColumn;
|
||||
import ghidra.feature.vt.api.impl.MarkupItemStorage;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class VTMatchMarkupItemTableDBAdapter {
|
||||
|
||||
public static class MarkupTableDescriptor extends db.util.TableDescriptor {
|
||||
public static TableColumn ASSOCIATION_KEY_COL = new TableColumn(LongField.class, true);
|
||||
public static TableColumn ADDRESS_SOURCE_COL = new TableColumn(StringField.class);
|
||||
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.class);
|
||||
public static TableColumn MARKUP_TYPE_COL = new TableColumn(ShortField.class);
|
||||
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.class);
|
||||
public static TableColumn SOURCE_VALUE_COL = new TableColumn(StringField.class);
|
||||
public static class MarkupTableDescriptor extends ghidra.feature.vt.api.db.TableDescriptor {
|
||||
public static TableColumn ASSOCIATION_KEY_COL = new TableColumn(LongField.INSTANCE, true);
|
||||
public static TableColumn ADDRESS_SOURCE_COL = new TableColumn(StringField.INSTANCE);
|
||||
public static TableColumn DESTINATION_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
|
||||
public static TableColumn MARKUP_TYPE_COL = new TableColumn(ShortField.INSTANCE);
|
||||
public static TableColumn SOURCE_ADDRESS_COL = new TableColumn(LongField.INSTANCE);
|
||||
public static TableColumn SOURCE_VALUE_COL = new TableColumn(StringField.INSTANCE);
|
||||
public static TableColumn ORIGINAL_DESTINATION_VALUE_COL =
|
||||
new TableColumn(StringField.class);
|
||||
public static TableColumn STATUS_COL = new TableColumn(ByteField.class);
|
||||
public static TableColumn STATUS_DESCRIPTION_COL = new TableColumn(StringField.class);
|
||||
new TableColumn(StringField.INSTANCE);
|
||||
public static TableColumn STATUS_COL = new TableColumn(ByteField.INSTANCE);
|
||||
public static TableColumn STATUS_DESCRIPTION_COL = new TableColumn(StringField.INSTANCE);
|
||||
|
||||
public static MarkupTableDescriptor INSTANCE = new MarkupTableDescriptor();
|
||||
}
|
||||
|
||||
protected static String TABLE_NAME = "MatchMarkupItemTable";
|
||||
static Schema TABLE_SCHEMA =
|
||||
new Schema(0, "Key", INSTANCE.getColumnClasses(), INSTANCE.getColumnNames());
|
||||
new Schema(0, "Key", INSTANCE.getColumnFields(), INSTANCE.getColumnNames());
|
||||
|
||||
protected static int[] INDEXED_COLUMNS = INSTANCE.getIndexedColumns();
|
||||
|
||||
@@ -71,6 +69,5 @@ public abstract class VTMatchMarkupItemTableDBAdapter {
|
||||
|
||||
public abstract int getRecordCount();
|
||||
|
||||
public abstract Record createMarkupItemRecord(MarkupItemStorage markupItem)
|
||||
throws IOException;
|
||||
public abstract Record createMarkupItemRecord(MarkupItemStorage markupItem) throws IOException;
|
||||
}
|
||||
|
||||
+19
-23
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,34 +15,33 @@
|
||||
*/
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import db.*;
|
||||
import ghidra.feature.vt.api.main.VTProgramCorrelator;
|
||||
import ghidra.program.database.map.AddressMap;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import db.*;
|
||||
|
||||
public abstract class VTMatchSetTableDBAdapter {
|
||||
|
||||
public enum ColumnDescription {
|
||||
CORRELATOR_CLASS_COL(StringField.class),
|
||||
CORRELATOR_NAME_COL(StringField.class),
|
||||
OPTIONS_COL(StringField.class);
|
||||
CORRELATOR_CLASS_COL(StringField.INSTANCE),
|
||||
CORRELATOR_NAME_COL(StringField.INSTANCE),
|
||||
OPTIONS_COL(StringField.INSTANCE);
|
||||
|
||||
private final Class<? extends Field> columnClass;
|
||||
private final Field columnField;
|
||||
|
||||
private ColumnDescription(Class<? extends Field> columnClass) {
|
||||
this.columnClass = columnClass;
|
||||
private ColumnDescription(Field columnField) {
|
||||
this.columnField = columnField;
|
||||
|
||||
}
|
||||
|
||||
public Class<? extends Field> getColumnClass() {
|
||||
return columnClass;
|
||||
public Field getColumnField() {
|
||||
return columnField;
|
||||
}
|
||||
|
||||
public int column() {
|
||||
@@ -59,20 +57,18 @@ public abstract class VTMatchSetTableDBAdapter {
|
||||
return list.toArray(new String[columns.length]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// we know our class types are safe
|
||||
private static Class<? extends Field>[] getColumnClasses() {
|
||||
private static Field[] getColumnFields() {
|
||||
ColumnDescription[] columns = ColumnDescription.values();
|
||||
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
|
||||
for (ColumnDescription column : columns) {
|
||||
list.add(column.getColumnClass());
|
||||
Field[] fields = new Field[columns.length];
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
fields[i] = columns[i].getColumnField();
|
||||
}
|
||||
return list.toArray(new Class[columns.length]);
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
static String TABLE_NAME = "MatchSetTable";
|
||||
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnClasses(),
|
||||
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
|
||||
ColumnDescription.getColumnNames());
|
||||
|
||||
static VTMatchSetTableDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
|
||||
|
||||
+13
-11
@@ -16,13 +16,6 @@
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import static ghidra.feature.vt.api.db.VTMatchSetTableDBAdapter.ColumnDescription.*;
|
||||
import ghidra.feature.vt.api.main.VTProgramCorrelator;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.program.database.map.AddressMap;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.xml.GenericXMLOutputter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
@@ -31,13 +24,20 @@ import org.jdom.Element;
|
||||
import org.jdom.output.XMLOutputter;
|
||||
|
||||
import db.*;
|
||||
import ghidra.feature.vt.api.main.VTProgramCorrelator;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.program.database.map.AddressMap;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.xml.GenericXMLOutputter;
|
||||
|
||||
public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
|
||||
|
||||
private Table table;
|
||||
|
||||
private static final Schema STORED_ADDRESS_RANGE_SCHEMA = new Schema(0, "Key", new Class[] {
|
||||
LongField.class, LongField.class }, new String[] { "addr1", "addr2" });
|
||||
private static final Schema STORED_ADDRESS_RANGE_SCHEMA = new Schema(0, "Key",
|
||||
new Field[] { LongField.INSTANCE, LongField.INSTANCE }, new String[] { "addr1", "addr2" });
|
||||
|
||||
private final DBHandle dbHandle;
|
||||
|
||||
@@ -46,7 +46,8 @@ public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
|
||||
table = dbHandle.createTable(TABLE_NAME, TABLE_SCHEMA);
|
||||
}
|
||||
|
||||
public VTMatchSetTableDBAdapterV0(DBHandle dbHandle, OpenMode openMode) throws VersionException {
|
||||
public VTMatchSetTableDBAdapterV0(DBHandle dbHandle, OpenMode openMode)
|
||||
throws VersionException {
|
||||
this.dbHandle = dbHandle;
|
||||
table = dbHandle.getTable(TABLE_NAME);
|
||||
if (table == null) {
|
||||
@@ -59,7 +60,8 @@ public class VTMatchSetTableDBAdapterV0 extends VTMatchSetTableDBAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record createMatchSetRecord(long key, VTProgramCorrelator correlator) throws IOException {
|
||||
public Record createMatchSetRecord(long key, VTProgramCorrelator correlator)
|
||||
throws IOException {
|
||||
Record record = TABLE_SCHEMA.createRecord(key);
|
||||
|
||||
record.setString(CORRELATOR_CLASS_COL.column(), correlator.getClass().getName());
|
||||
|
||||
+23
-28
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,36 +15,35 @@
|
||||
*/
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import ghidra.feature.vt.api.main.VTMatchInfo;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import db.*;
|
||||
import ghidra.feature.vt.api.main.VTMatchInfo;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class VTMatchTableDBAdapter {
|
||||
|
||||
public enum ColumnDescription {
|
||||
TAG_KEY_COL(LongField.class),
|
||||
MATCH_SET_COL(LongField.class),
|
||||
SIMILARITY_SCORE_COL(StringField.class),
|
||||
CONFIDENCE_SCORE_COL(StringField.class),
|
||||
LENGTH_TYPE(StringField.class),
|
||||
SOURCE_LENGTH_COL(IntField.class),
|
||||
DESTINATION_LENGTH_COL(IntField.class),
|
||||
ASSOCIATION_COL(LongField.class);
|
||||
TAG_KEY_COL(LongField.INSTANCE),
|
||||
MATCH_SET_COL(LongField.INSTANCE),
|
||||
SIMILARITY_SCORE_COL(StringField.INSTANCE),
|
||||
CONFIDENCE_SCORE_COL(StringField.INSTANCE),
|
||||
LENGTH_TYPE(StringField.INSTANCE),
|
||||
SOURCE_LENGTH_COL(IntField.INSTANCE),
|
||||
DESTINATION_LENGTH_COL(IntField.INSTANCE),
|
||||
ASSOCIATION_COL(LongField.INSTANCE);
|
||||
|
||||
private final Class<? extends Field> columnClass;
|
||||
private final Field columnField;
|
||||
|
||||
private ColumnDescription(Class<? extends Field> columnClass) {
|
||||
this.columnClass = columnClass;
|
||||
private ColumnDescription(Field columnField) {
|
||||
this.columnField = columnField;
|
||||
}
|
||||
|
||||
public Class<? extends Field> getColumnClass() {
|
||||
return columnClass;
|
||||
public Field getColumnField() {
|
||||
return columnField;
|
||||
}
|
||||
|
||||
public int column() {
|
||||
@@ -61,22 +59,19 @@ public abstract class VTMatchTableDBAdapter {
|
||||
return list.toArray(new String[columns.length]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// we know our class types are safe
|
||||
private static Class<? extends Field>[] getColumnClasses() {
|
||||
private static Field[] getColumnFields() {
|
||||
ColumnDescription[] columns = ColumnDescription.values();
|
||||
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
|
||||
for (ColumnDescription column : columns) {
|
||||
list.add(column.getColumnClass());
|
||||
Field[] fields = new Field[columns.length];
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
fields[i] = columns[i].getColumnField();
|
||||
}
|
||||
return list.toArray(new Class[columns.length]);
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
static String TABLE_NAME = "MatchTable";
|
||||
static Schema TABLE_SCHEMA =
|
||||
new Schema(0, "Key", ColumnDescription.getColumnClasses(),
|
||||
ColumnDescription.getColumnNames());
|
||||
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
|
||||
ColumnDescription.getColumnNames());
|
||||
|
||||
static VTMatchTableDBAdapter createAdapter(DBHandle dbHandle, long tableID) throws IOException {
|
||||
return new VTMatchTableDBAdapterV0(dbHandle, tableID);
|
||||
|
||||
+15
-20
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,14 +15,13 @@
|
||||
*/
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import db.*;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Abstract adapter for the database table that holds tags for version tracking matches.
|
||||
@@ -31,16 +29,16 @@ import db.*;
|
||||
public abstract class VTMatchTagDBAdapter {
|
||||
|
||||
public enum ColumnDescription {
|
||||
TAG_NAME_COL(StringField.class);
|
||||
TAG_NAME_COL(StringField.INSTANCE);
|
||||
|
||||
private final Class<? extends Field> columnClass;
|
||||
private final Field columnField;
|
||||
|
||||
private ColumnDescription(Class<? extends Field> columnClass) {
|
||||
this.columnClass = columnClass;
|
||||
private ColumnDescription(Field columnField) {
|
||||
this.columnField = columnField;
|
||||
}
|
||||
|
||||
public Class<? extends Field> getColumnClass() {
|
||||
return columnClass;
|
||||
public Field getColumnField() {
|
||||
return columnField;
|
||||
}
|
||||
|
||||
public int column() {
|
||||
@@ -56,22 +54,19 @@ public abstract class VTMatchTagDBAdapter {
|
||||
return list.toArray(new String[columns.length]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// we know our class types are safe
|
||||
private static Class<? extends Field>[] getColumnClasses() {
|
||||
private static Field[] getColumnFields() {
|
||||
ColumnDescription[] columns = ColumnDescription.values();
|
||||
List<Class<? extends Field>> list = new LinkedList<Class<? extends Field>>();
|
||||
for (ColumnDescription column : columns) {
|
||||
list.add(column.getColumnClass());
|
||||
Field[] fields = new Field[columns.length];
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
fields[i] = columns[i].getColumnField();
|
||||
}
|
||||
return list.toArray(new Class[columns.length]);
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
||||
static String TABLE_NAME = "MatchTagTable";
|
||||
static Schema TABLE_SCHEMA =
|
||||
new Schema(0, LongField.class, "Key", ColumnDescription.getColumnClasses(),
|
||||
ColumnDescription.getColumnNames());
|
||||
static Schema TABLE_SCHEMA = new Schema(0, "Key", ColumnDescription.getColumnFields(),
|
||||
ColumnDescription.getColumnNames());
|
||||
|
||||
static VTMatchTagDBAdapter createAdapter(DBHandle dbHandle) throws IOException {
|
||||
return new VTMatchTagDBAdapterV0(dbHandle);
|
||||
|
||||
+39
-14
@@ -39,10 +39,10 @@ import ghidra.util.exception.*;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTChangeManager {
|
||||
private final static Class<?>[] COL_CLASS = new Class[] { StringField.class };
|
||||
private final static Field[] COL_FIELDS = new Field[] { StringField.INSTANCE };
|
||||
private final static String[] COL_TYPES = new String[] { "Value" };
|
||||
private final static Schema SCHEMA =
|
||||
new Schema(0, StringField.class, "Key", COL_CLASS, COL_TYPES);
|
||||
new Schema(0, StringField.INSTANCE, "Key", COL_FIELDS, COL_TYPES);
|
||||
|
||||
private static final String PROGRAM_ID_PROPERTYLIST_NAME = "ProgramIDs";
|
||||
private static final String SOURCE_PROGRAM_ID_PROPERTY_KEY = "SourceProgramID";
|
||||
@@ -55,7 +55,24 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
|
||||
private static final long IMPLIED_MATCH_SET_ID = -1;
|
||||
private static final String PROPERTY_TABLE_NAME = "PropertyTable";
|
||||
private static final String DB_VERSION_PROPERTY_NAME = "DB_VERSION";
|
||||
private static final int DB_VERSION = 1;
|
||||
|
||||
/**
|
||||
* DB_VERSION should be incremented any time a change is made to the overall
|
||||
* database schema associated with any of the adapters.
|
||||
* 14-Nov-2019 - version 2 - Corrected fixed length indexing implementation causing
|
||||
* change in index table low-level storage for newly
|
||||
* created tables.
|
||||
*/
|
||||
private static final int DB_VERSION = 2;
|
||||
|
||||
/**
|
||||
* UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION any time the
|
||||
* latest version requires a forced upgrade (i.e., Read-only mode not supported
|
||||
* until upgrade is performed). It is assumed that read-only mode is supported
|
||||
* if the data's version is >= UPGRADE_REQUIRED_BEFORE_VERSION and <= DB_VERSION.
|
||||
*/
|
||||
// NOTE: Schema upgrades are not currently supported
|
||||
private static final int UPGRADE_REQUIRED_BEFORE_VERSION = 1;
|
||||
|
||||
private VTMatchSetTableDBAdapter matchSetTableAdapter;
|
||||
private AssociationDatabaseManager associationManager;
|
||||
@@ -78,12 +95,11 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
|
||||
|
||||
int ID = session.startTransaction("Constructing New Version Tracking Match Set");
|
||||
try {
|
||||
session.propertyTable = createPropertyTable(session.getDBHandle());
|
||||
session.matchSetTableAdapter =
|
||||
VTMatchSetTableDBAdapter.createAdapter(session.getDBHandle());
|
||||
session.propertyTable = session.dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
|
||||
session.matchSetTableAdapter = VTMatchSetTableDBAdapter.createAdapter(session.dbh);
|
||||
session.associationManager =
|
||||
AssociationDatabaseManager.createAssociationManager(session.getDBHandle(), session);
|
||||
session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.getDBHandle());
|
||||
AssociationDatabaseManager.createAssociationManager(session.dbh, session);
|
||||
session.matchTagAdapter = VTMatchTagDBAdapter.createAdapter(session.dbh);
|
||||
session.initializePrograms(sourceProgram, destinationProgram);
|
||||
session.createMatchSet(
|
||||
new ManualMatchProgramCorrelator(sourceProgram, destinationProgram),
|
||||
@@ -91,6 +107,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
|
||||
session.createMatchSet(
|
||||
new ImpliedMatchProgramCorrelator(sourceProgram, destinationProgram),
|
||||
IMPLIED_MATCH_SET_ID);
|
||||
session.updateVersion();
|
||||
}
|
||||
finally {
|
||||
session.endTransaction(ID, true);
|
||||
@@ -105,21 +122,29 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession, VTC
|
||||
return session;
|
||||
}
|
||||
|
||||
private static Table createPropertyTable(DBHandle dbh) throws IOException {
|
||||
Table table = dbh.createTable(PROPERTY_TABLE_NAME, SCHEMA);
|
||||
private void updateVersion() throws IOException {
|
||||
Record record = SCHEMA.createRecord(new StringField(DB_VERSION_PROPERTY_NAME));
|
||||
record.setString(0, Integer.toString(DB_VERSION));
|
||||
table.putRecord(record);
|
||||
return table;
|
||||
propertyTable.putRecord(record);
|
||||
}
|
||||
|
||||
public static VTSessionDB getVTSession(DBHandle dbHandle, OpenMode openMode, Object consumer,
|
||||
TaskMonitor monitor) throws VersionException, IOException {
|
||||
|
||||
VTSessionDB session = new VTSessionDB(dbHandle, consumer);
|
||||
if (session.getVersion() < DB_VERSION) {
|
||||
throw new VersionException("Version Tracking Sessions do not support upgrades.");
|
||||
int storedVersion = session.getVersion();
|
||||
|
||||
if (storedVersion > DB_VERSION) {
|
||||
throw new VersionException(VersionException.NEWER_VERSION, false);
|
||||
}
|
||||
// The following version logic holds true for DB_VERSION=2 which assumes no additional
|
||||
// DB index tables will be added when open for update/upgrade. This will not hold
|
||||
// true for future revisions associated with table schema changes in which case the
|
||||
// UPGRADE_REQUIRED_BEFORE_VERSION value should equal DB_VERSION.
|
||||
if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) {
|
||||
throw new VersionException("Version Tracking Sessions do not support schema upgrades.");
|
||||
}
|
||||
|
||||
session.matchSetTableAdapter =
|
||||
VTMatchSetTableDBAdapter.getAdapter(session.getDBHandle(), openMode, monitor);
|
||||
session.associationManager =
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,12 +15,11 @@
|
||||
*/
|
||||
package db;
|
||||
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* <code>BTreeNode</code> defines a common interface for all types
|
||||
@@ -30,17 +28,22 @@ import db.buffers.DataBuffer;
|
||||
interface BTreeNode {
|
||||
|
||||
/**
|
||||
* Return the data buffer ID associated with this node.
|
||||
* @return the parent node or null if this is the root
|
||||
*/
|
||||
public InteriorNode getParent();
|
||||
|
||||
/**
|
||||
* @return the data buffer ID associated with this node.
|
||||
*/
|
||||
public int getBufferId();
|
||||
|
||||
/**
|
||||
* Return the data buffer associated with this node.
|
||||
* @return the data buffer associated with this node.
|
||||
*/
|
||||
public DataBuffer getBuffer();
|
||||
|
||||
/**
|
||||
* Return the number of keys contained within this node.
|
||||
* @return the number of keys contained within this node.
|
||||
*/
|
||||
public int getKeyCount();
|
||||
|
||||
@@ -50,6 +53,26 @@ interface BTreeNode {
|
||||
*/
|
||||
public void setKeyCount(int cnt);
|
||||
|
||||
/**
|
||||
* Get the key value at a specific index.
|
||||
* @param index key index
|
||||
* @return key value
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public Field getKeyField(int index) throws IOException;
|
||||
|
||||
/**
|
||||
* Perform a binary search to locate the specified key and derive an index
|
||||
* into the Buffer ID storage. This method is intended to find the insertion
|
||||
* index or exact match for a child key. A negative value will be returned
|
||||
* when an exact match is not found and may be transformed into an
|
||||
* insertion index (insetIndex = -returnedIndex-1).
|
||||
* @param key key to search for
|
||||
* @return int buffer ID index.
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public int getKeyIndex(Field key) throws IOException;
|
||||
|
||||
/**
|
||||
* Delete this node and all child nodes.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
@@ -67,11 +90,12 @@ interface BTreeNode {
|
||||
* Check the consistency of this node and all of its children.
|
||||
* @return true if consistency check passed, else false
|
||||
* @param tableName name of table containing this node
|
||||
* @param monitor
|
||||
* @throws IOException
|
||||
* @param monitor task monitor
|
||||
* @throws IOException if IO error occured
|
||||
* @throws CancelledException if task cancelled
|
||||
* @{@link ThrowsTag} CancelledException
|
||||
*/
|
||||
public boolean isConsistent(String tableName, TaskMonitor monitor) throws IOException,
|
||||
CancelledException;
|
||||
public boolean isConsistent(String tableName, TaskMonitor monitor)
|
||||
throws IOException, CancelledException;
|
||||
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package db;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* Allows various non-database supported data types to be
|
||||
* encoded within a BinaryField which may be stored within the
|
||||
@@ -31,63 +31,63 @@ import java.util.ArrayList;
|
||||
* support a byte array.
|
||||
*/
|
||||
public class BinaryCodedField extends BinaryField {
|
||||
|
||||
|
||||
/**
|
||||
* byte[] data type
|
||||
*/
|
||||
public static final byte BYTE_ARRAY = 0;
|
||||
|
||||
|
||||
/**
|
||||
* float data type
|
||||
*/
|
||||
public static final byte FLOAT = 1;
|
||||
|
||||
|
||||
/**
|
||||
* double data type
|
||||
*/
|
||||
public static final byte DOUBLE = 2;
|
||||
|
||||
|
||||
/**
|
||||
* short data type
|
||||
*/
|
||||
public static final byte SHORT_ARRAY = 3;
|
||||
|
||||
|
||||
/**
|
||||
* int[] data type
|
||||
*/
|
||||
public static final byte INT_ARRAY = 4;
|
||||
|
||||
|
||||
/**
|
||||
* long[] data type
|
||||
*/
|
||||
public static final byte LONG_ARRAY = 5;
|
||||
|
||||
|
||||
/**
|
||||
* float[] data type
|
||||
*/
|
||||
public static final byte FLOAT_ARRAY = 6;
|
||||
|
||||
|
||||
/**
|
||||
* double[] data type
|
||||
*/
|
||||
public static final byte DOUBLE_ARRAY = 7;
|
||||
|
||||
|
||||
/**
|
||||
* String[] data type
|
||||
*/
|
||||
public static final byte STRING_ARRAY = 8;
|
||||
|
||||
|
||||
private static final int DATA_TYPE_OFFSET = 0;
|
||||
private static final int DATA_OFFSET = 1;
|
||||
|
||||
|
||||
private static final String STRING_ENCODING = "UTF-8";
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
BinaryCodedField() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from an existing binary field.
|
||||
* @param binField the binary field
|
||||
@@ -95,7 +95,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
public BinaryCodedField(BinaryField binField) {
|
||||
data = binField.getBinaryData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a double value.
|
||||
* @param value the double value
|
||||
@@ -106,7 +106,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
buffer.putLong(DATA_OFFSET, Double.doubleToLongBits(value));
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a float value.
|
||||
* @param value the float value
|
||||
@@ -117,7 +117,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
buffer.putInt(DATA_OFFSET, Float.floatToIntBits(value));
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a byte array.
|
||||
* @param values byte array
|
||||
@@ -125,121 +125,121 @@ public class BinaryCodedField extends BinaryField {
|
||||
public BinaryCodedField(byte[] values) {
|
||||
if (values != null) {
|
||||
data = new byte[values.length + 2];
|
||||
data[DATA_OFFSET] = (byte)0;
|
||||
data[DATA_OFFSET] = (byte) 0;
|
||||
System.arraycopy(values, 0, data, 2, values.length);
|
||||
}
|
||||
else {
|
||||
data = new byte[2];
|
||||
data[DATA_OFFSET] = (byte)-1;
|
||||
data[DATA_OFFSET] = (byte) -1;
|
||||
}
|
||||
data[DATA_TYPE_OFFSET] = BYTE_ARRAY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a short array.
|
||||
* @param values short array
|
||||
*/
|
||||
public BinaryCodedField(short[] values) {
|
||||
int len = (values != null ? (2*values.length) : 0) + 2;
|
||||
int len = (values != null ? (2 * values.length) : 0) + 2;
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
|
||||
buffer.putByte(DATA_TYPE_OFFSET, SHORT_ARRAY);
|
||||
if (values != null) {
|
||||
int offset = DATA_OFFSET;
|
||||
buffer.putByte(offset++, (byte)0);
|
||||
buffer.putByte(offset++, (byte) 0);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
offset = buffer.putShort(offset, values[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
buffer.putByte(DATA_OFFSET, (byte)-1);
|
||||
buffer.putByte(DATA_OFFSET, (byte) -1);
|
||||
}
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a int array.
|
||||
* @param values int array
|
||||
*/
|
||||
public BinaryCodedField(int[] values) {
|
||||
int len = (values != null ? (4*values.length) : 0) + 2;
|
||||
int len = (values != null ? (4 * values.length) : 0) + 2;
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
|
||||
buffer.putByte(DATA_TYPE_OFFSET, INT_ARRAY);
|
||||
if (values != null) {
|
||||
int offset = DATA_OFFSET;
|
||||
buffer.putByte(offset++, (byte)0);
|
||||
buffer.putByte(offset++, (byte) 0);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
offset = buffer.putInt(offset, values[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
buffer.putByte(DATA_OFFSET, (byte)-1);
|
||||
buffer.putByte(DATA_OFFSET, (byte) -1);
|
||||
}
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a long array.
|
||||
* @param values long array
|
||||
*/
|
||||
public BinaryCodedField(long[] values) {
|
||||
int len = (values != null ? (8*values.length) : 0) + 2;
|
||||
int len = (values != null ? (8 * values.length) : 0) + 2;
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
|
||||
buffer.putByte(DATA_TYPE_OFFSET, LONG_ARRAY);
|
||||
if (values != null) {
|
||||
int offset = DATA_OFFSET;
|
||||
buffer.putByte(offset++, (byte)0);
|
||||
buffer.putByte(offset++, (byte) 0);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
offset = buffer.putLong(offset, values[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
buffer.putByte(DATA_OFFSET, (byte)-1);
|
||||
buffer.putByte(DATA_OFFSET, (byte) -1);
|
||||
}
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a float array.
|
||||
* @param values float array
|
||||
*/
|
||||
public BinaryCodedField(float[] values) {
|
||||
int len = (values != null ? (4*values.length) : 0) + 2;
|
||||
int len = (values != null ? (4 * values.length) : 0) + 2;
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
|
||||
buffer.putByte(DATA_TYPE_OFFSET, FLOAT_ARRAY);
|
||||
if (values != null) {
|
||||
int offset = DATA_OFFSET;
|
||||
buffer.putByte(offset++, (byte)0);
|
||||
buffer.putByte(offset++, (byte) 0);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
offset = buffer.putInt(offset, Float.floatToIntBits(values[i]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
buffer.putByte(DATA_OFFSET, (byte)-1);
|
||||
buffer.putByte(DATA_OFFSET, (byte) -1);
|
||||
}
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a double array.
|
||||
* @param values double array
|
||||
*/
|
||||
public BinaryCodedField(double[] values) {
|
||||
int len = (values != null ? (8*values.length) : 0) + 2;
|
||||
int len = (values != null ? (8 * values.length) : 0) + 2;
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(len);
|
||||
buffer.putByte(DATA_TYPE_OFFSET, DOUBLE_ARRAY);
|
||||
if (values != null) {
|
||||
int offset = DATA_OFFSET;
|
||||
buffer.putByte(offset++, (byte)0);
|
||||
buffer.putByte(offset++, (byte) 0);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
offset = buffer.putLong(offset, Double.doubleToLongBits(values[i]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
buffer.putByte(DATA_OFFSET, (byte)-1);
|
||||
buffer.putByte(DATA_OFFSET, (byte) -1);
|
||||
}
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a coded field from a String array.
|
||||
* @param strings String array
|
||||
@@ -256,29 +256,31 @@ public class BinaryCodedField extends BinaryField {
|
||||
}
|
||||
buffer = new BinaryDataBuffer(len);
|
||||
int offset = DATA_OFFSET;
|
||||
buffer.putByte(offset++, (byte)0);
|
||||
buffer.putByte(offset++, (byte) 0);
|
||||
try {
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
if (strings[i] == null) {
|
||||
offset = buffer.putInt(offset, -1);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
byte[] bytes = strings[i].getBytes(STRING_ENCODING);
|
||||
offset = buffer.putInt(offset, bytes.length);
|
||||
offset = buffer.put(offset, bytes);
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new AssertException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
buffer = new BinaryDataBuffer(2);
|
||||
buffer.putByte(DATA_OFFSET, (byte)-1);
|
||||
buffer.putByte(DATA_OFFSET, (byte) -1);
|
||||
}
|
||||
buffer.putByte(DATA_TYPE_OFFSET, STRING_ARRAY);
|
||||
data = buffer.getData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the data type associated with this field.
|
||||
* @return data type
|
||||
@@ -286,7 +288,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
public byte getDataType() {
|
||||
return data[DATA_TYPE_OFFSET];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the double value contained with this field.
|
||||
* @return double value
|
||||
@@ -299,7 +301,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
|
||||
return Double.longBitsToDouble(buffer.getLong(DATA_OFFSET));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the float value contained with this field.
|
||||
* @return float value
|
||||
@@ -312,7 +314,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
|
||||
return Float.intBitsToFloat(buffer.getInt(DATA_OFFSET));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the byte array contained with this field.
|
||||
* @return byte array
|
||||
@@ -329,7 +331,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
System.arraycopy(data, 2, values, 0, values.length);
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the short array contained with this field.
|
||||
* @return short array
|
||||
@@ -342,7 +344,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
if (data[DATA_OFFSET] < 0) {
|
||||
return null;
|
||||
}
|
||||
short[] values = new short[(data.length -2) / 2];
|
||||
short[] values = new short[(data.length - 2) / 2];
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
|
||||
int offset = DATA_OFFSET + 1;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
@@ -351,7 +353,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the int array contained with this field.
|
||||
* @return int array
|
||||
@@ -364,7 +366,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
if (data[DATA_OFFSET] < 0) {
|
||||
return null;
|
||||
}
|
||||
int[] values = new int[(data.length -2) / 4];
|
||||
int[] values = new int[(data.length - 2) / 4];
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
|
||||
int offset = DATA_OFFSET + 1;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
@@ -373,7 +375,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the long array contained with this field.
|
||||
* @return long array
|
||||
@@ -386,7 +388,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
if (data[DATA_OFFSET] < 0) {
|
||||
return null;
|
||||
}
|
||||
long[] values = new long[(data.length -2) / 8];
|
||||
long[] values = new long[(data.length - 2) / 8];
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
|
||||
int offset = DATA_OFFSET + 1;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
@@ -395,7 +397,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the float array contained with this field.
|
||||
* @return float array
|
||||
@@ -408,7 +410,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
if (data[DATA_OFFSET] < 0) {
|
||||
return null;
|
||||
}
|
||||
float[] values = new float[(data.length -2) / 4];
|
||||
float[] values = new float[(data.length - 2) / 4];
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
|
||||
int offset = DATA_OFFSET + 1;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
@@ -417,7 +419,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the double array contained with this field.
|
||||
* @return double array
|
||||
@@ -430,7 +432,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
if (data[DATA_OFFSET] < 0) {
|
||||
return null;
|
||||
}
|
||||
double[] values = new double[(data.length -2) / 8];
|
||||
double[] values = new double[(data.length - 2) / 8];
|
||||
BinaryDataBuffer buffer = new BinaryDataBuffer(data);
|
||||
int offset = DATA_OFFSET + 1;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
@@ -439,7 +441,7 @@ public class BinaryCodedField extends BinaryField {
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the String array contained with this field.
|
||||
* @return String array
|
||||
@@ -463,11 +465,13 @@ public class BinaryCodedField extends BinaryField {
|
||||
byte[] bytes = buffer.get(offset, len);
|
||||
strList.add(new String(bytes, STRING_ENCODING));
|
||||
offset += len;
|
||||
} else {
|
||||
strList.add(null);
|
||||
}
|
||||
else {
|
||||
strList.add(null);
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new AssertException();
|
||||
}
|
||||
String[] strings = new String[strList.size()];
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,13 +18,21 @@ package db;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
|
||||
/**
|
||||
* <code>BinaryField</code> provides a wrapper for variable length binary data which is read or
|
||||
* written to a Record.
|
||||
*/
|
||||
public class BinaryField extends Field {
|
||||
|
||||
/**
|
||||
* Instance intended for defining a {@link Table} {@link Schema}
|
||||
*/
|
||||
public static final BinaryField INSTANCE = new BinaryField(null, true);
|
||||
|
||||
protected byte[] data;
|
||||
private Integer hashcode;
|
||||
|
||||
/**
|
||||
* Construct a binary data field with an initial value of null.
|
||||
@@ -38,36 +45,41 @@ public class BinaryField extends Field {
|
||||
* @param data initial value
|
||||
*/
|
||||
public BinaryField(byte[] data) {
|
||||
this(data, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a binary data field with an initial value of data.
|
||||
* @param data initial value
|
||||
* @param immutable true if field value is immutable
|
||||
*/
|
||||
BinaryField(byte[] data, boolean immutable) {
|
||||
super(immutable);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getBinaryData()
|
||||
*/
|
||||
@Override
|
||||
void checkImmutable() {
|
||||
super.checkImmutable();
|
||||
hashcode = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBinaryData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#setBinaryData(byte[])
|
||||
*/
|
||||
@Override
|
||||
public void setBinaryData(byte[] data) {
|
||||
checkImmutable();
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#length()
|
||||
*/
|
||||
@Override
|
||||
int length() {
|
||||
return (data == null) ? 4 : (data.length + 4);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int write(Buffer buf, int offset) throws IOException {
|
||||
if (data == null) {
|
||||
@@ -77,11 +89,9 @@ public class BinaryField extends Field {
|
||||
return buf.put(offset, data);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int read(Buffer buf, int offset) throws IOException {
|
||||
checkImmutable();
|
||||
int len = buf.getInt(offset);
|
||||
offset += 4;
|
||||
if (len < 0) {
|
||||
@@ -94,97 +104,25 @@ public class BinaryField extends Field {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int readLength(Buffer buf, int offset) throws IOException {
|
||||
int len = buf.getInt(offset);
|
||||
return (len < 0 ? 0 : len) + 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#isVariableLength()
|
||||
*/
|
||||
@Override
|
||||
public boolean isVariableLength() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getFieldType()
|
||||
*/
|
||||
@Override
|
||||
protected byte getFieldType() {
|
||||
byte getFieldType() {
|
||||
return BINARY_OBJ_TYPE;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (data == null) {
|
||||
return "BinaryField: null";
|
||||
}
|
||||
return "BinaryField[" + data.length + "] = " + getValueAsString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
int i = 0;
|
||||
for (; i < 24 && i < data.length; i++) {
|
||||
String b = Integer.toHexString(data[i] & 0xff);
|
||||
if (b.length() == 1) {
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(b);
|
||||
buf.append(' ');
|
||||
}
|
||||
if (i < data.length) {
|
||||
buf.append("...");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof BinaryField))
|
||||
return false;
|
||||
BinaryField f = (BinaryField) obj;
|
||||
return Arrays.equals(f.data, data);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Get first 8 bytes of data as long value.
|
||||
// * First data byte corresponds to most significant byte
|
||||
// * of long value so that proper sign is preserved.
|
||||
// * If data is null, Long.MIN_VALUE is returned.
|
||||
// * @see ghidra.framework.store.db.Field#getLongValue()
|
||||
// */
|
||||
// public long getLongValue() {
|
||||
// long value = 0;
|
||||
// if (data == null) {
|
||||
// return Long.MIN_VALUE;
|
||||
// }
|
||||
// for (int i = 0; i < 8 && i < data.length; i++) {
|
||||
// value = (value << 8) | ((long)data[i] & 0x000000ff);
|
||||
// }
|
||||
// if (data.length < 8) {
|
||||
// value = value << (8 * (8 - data.length));
|
||||
// }
|
||||
// return value;
|
||||
// }
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#truncate(int)
|
||||
*/
|
||||
@Override
|
||||
void truncate(int length) {
|
||||
checkImmutable();
|
||||
int maxLen = length - 4;
|
||||
if (data != null && data.length > maxLen) {
|
||||
byte[] newData = new byte[maxLen];
|
||||
@@ -193,9 +131,6 @@ public class BinaryField extends Field {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Field o) {
|
||||
BinaryField f = (BinaryField) o;
|
||||
@@ -224,28 +159,105 @@ public class BinaryField extends Field {
|
||||
return len1 - len2;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
|
||||
*/
|
||||
@Override
|
||||
public Field newField(Field fieldValue) {
|
||||
return new BinaryField(fieldValue.getBinaryData());
|
||||
int compareTo(DataBuffer buffer, int offset) {
|
||||
int len = buffer.getInt(offset);
|
||||
if (data == null) {
|
||||
if (len < 0) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
else if (len < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -buffer.unsignedCompareTo(data, offset + 4, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#newField()
|
||||
*/
|
||||
@Override
|
||||
public Field newField() {
|
||||
public BinaryField copyField() {
|
||||
return new BinaryField(getBinaryData().clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryField newField() {
|
||||
return new BinaryField();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
BinaryField getMinValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
BinaryField getMaxValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || obj.getClass() != getClass())
|
||||
return false;
|
||||
BinaryField f = (BinaryField) obj;
|
||||
return Arrays.equals(f.data, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return data.hashCode();
|
||||
if (hashcode == null) {
|
||||
int h = 0;
|
||||
if (data != null) {
|
||||
for (byte b : data) {
|
||||
h = 31 * h + (b & 0xff);
|
||||
}
|
||||
}
|
||||
hashcode = h;
|
||||
}
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
/// Methods below should not use data field directly
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String classname = getClass().getSimpleName();
|
||||
byte[] d = getBinaryData();
|
||||
if (d == null) {
|
||||
return classname + ": null";
|
||||
}
|
||||
return classname = "[" + d.length + "] = 0x" + getValueAsString(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
byte[] d = getBinaryData();
|
||||
if (d == null) {
|
||||
return "null";
|
||||
}
|
||||
return "{" + getValueAsString(d) + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get format value string for byte array
|
||||
* @param data byte array
|
||||
* @return formatted value string
|
||||
*/
|
||||
public static String getValueAsString(byte[] data) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
int i = 0;
|
||||
for (; i < 24 && i < data.length; i++) {
|
||||
String b = Integer.toHexString(data[i] & 0xff);
|
||||
if (b.length() == 1) {
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(b);
|
||||
buf.append(' ');
|
||||
}
|
||||
if (i < data.length) {
|
||||
buf.append("...");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,15 +15,30 @@
|
||||
*/
|
||||
package db;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
|
||||
/**
|
||||
* <code>BooleanField</code> provides a wrapper for boolean data which is read or
|
||||
* written to a Record.
|
||||
*/
|
||||
public class BooleanField extends Field {
|
||||
public final class BooleanField extends Field {
|
||||
|
||||
/**
|
||||
* Minimum boolean field value (FALSE)
|
||||
*/
|
||||
public static final BooleanField MIN_VALUE = new BooleanField(false, true);
|
||||
|
||||
/**
|
||||
* Maximum boolean field value (TRUE)
|
||||
*/
|
||||
public static final BooleanField MAX_VALUE = new BooleanField(true, true);
|
||||
|
||||
/**
|
||||
* Instance intended for defining a {@link Table} {@link Schema}
|
||||
*/
|
||||
public static final BooleanField INSTANCE = MIN_VALUE;
|
||||
|
||||
private byte value;
|
||||
|
||||
@@ -39,70 +53,57 @@ public class BooleanField extends Field {
|
||||
* @param b initial value
|
||||
*/
|
||||
public BooleanField(boolean b) {
|
||||
this(b, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a boolean data field with an initial value of b.
|
||||
* @param b initial value
|
||||
* @param immutable true if field value is immutable
|
||||
*/
|
||||
BooleanField(boolean b, boolean immutable) {
|
||||
super(immutable);
|
||||
value = b ? (byte) 1 : (byte) 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getBooleanValue()
|
||||
*/
|
||||
@Override
|
||||
public boolean getBooleanValue() {
|
||||
return (value == 0) ? false : true;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#setBooleanValue(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void setBooleanValue(boolean b) {
|
||||
|
||||
checkImmutable();
|
||||
this.value = b ? (byte) 1 : (byte) 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#length()
|
||||
*/
|
||||
@Override
|
||||
int length() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int write(Buffer buf, int offset) throws IOException {
|
||||
return buf.putByte(offset, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int read(Buffer buf, int offset) throws IOException {
|
||||
checkImmutable();
|
||||
value = buf.getByte(offset);
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int readLength(Buffer buf, int offset) throws IOException {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getFieldType()
|
||||
*/
|
||||
@Override
|
||||
protected byte getFieldType() {
|
||||
byte getFieldType() {
|
||||
return BOOLEAN_TYPE;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BooleanField: " + Boolean.toString(getBooleanValue());
|
||||
@@ -113,9 +114,6 @@ public class BooleanField extends Field {
|
||||
return Boolean.toString(getBooleanValue());
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof BooleanField))
|
||||
@@ -124,9 +122,6 @@ public class BooleanField extends Field {
|
||||
return otherField.value == value;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Field o) {
|
||||
BooleanField f = (BooleanField) o;
|
||||
@@ -137,44 +132,58 @@ public class BooleanField extends Field {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
|
||||
*/
|
||||
@Override
|
||||
public Field newField(Field fieldValue) {
|
||||
if (fieldValue.isVariableLength())
|
||||
throw new AssertException();
|
||||
return new BooleanField(fieldValue.getLongValue() != 0);
|
||||
int compareTo(DataBuffer buffer, int offset) {
|
||||
byte otherValue = buffer.getByte(offset);
|
||||
if (value == otherValue)
|
||||
return 0;
|
||||
else if (value < otherValue)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#newField()
|
||||
*/
|
||||
@Override
|
||||
public Field newField() {
|
||||
public BooleanField copyField() {
|
||||
return new BooleanField(getLongValue() != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanField newField() {
|
||||
return new BooleanField();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getLongValue()
|
||||
*/
|
||||
@Override
|
||||
public long getLongValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getBinaryData()
|
||||
*/
|
||||
@Override
|
||||
public byte[] getBinaryData() {
|
||||
return new byte[] { value };
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryData(byte[] bytes) {
|
||||
checkImmutable();
|
||||
if (bytes.length != 1) {
|
||||
throw new IllegalFieldAccessException();
|
||||
}
|
||||
value = bytes[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// TODO Auto-generated method stub
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
BooleanField getMinValue() {
|
||||
return MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
BooleanField getMaxValue() {
|
||||
return MAX_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,20 +22,20 @@ import java.io.IOException;
|
||||
* providing various data access methods.
|
||||
*/
|
||||
public interface Buffer {
|
||||
|
||||
|
||||
/**
|
||||
* Get the buffer ID for this buffer.
|
||||
* @return int
|
||||
*/
|
||||
public int getId();
|
||||
|
||||
|
||||
/**
|
||||
* Get the length of the buffer in bytes. The length reflects the number of
|
||||
* bytes which have been allocated to the buffer.
|
||||
* @return length of allocated buffer.
|
||||
*/
|
||||
public int length();
|
||||
|
||||
|
||||
/**
|
||||
* Get the byte data located at the specified offset and store into the
|
||||
* bytes array provided.
|
||||
@@ -62,10 +61,11 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public void get(int offset, byte[] data, int dataOffset, int length) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Get the byte data located at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
* @param length number of bytes to be read and returned
|
||||
* @return the byte array.
|
||||
* @throws ArrayIndexOutOfBoundsException is thrown if an invalid offset is
|
||||
* specified or the end of the buffer was encountered while reading the
|
||||
@@ -74,7 +74,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public byte[] get(int offset, int length) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Get the 8-bit byte value located at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -85,7 +85,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public byte getByte(int offset) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Get the 32-bit integer value located at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -97,7 +97,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public int getInt(int offset) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Get the 16-bit short value located at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -109,7 +109,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public short getShort(int offset) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Get the 64-bit long value located at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -121,7 +121,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public long getLong(int offset) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Put a specified number of bytes from the array provided into the buffer
|
||||
* at the specified offset. The number of bytes stored is specified by the
|
||||
@@ -153,7 +153,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public int put(int offset, byte[] bytes) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Put the 8-bit byte value into the buffer at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -165,7 +165,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public int putByte(int offset, byte b) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Put the 32-bit integer value into the buffer at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -178,7 +178,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public int putInt(int offset, int v) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Put the 16-bit short value into the buffer at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -191,7 +191,7 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public int putShort(int offset, short v) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Put the 64-bit long value into the buffer at the specified offset.
|
||||
* @param offset byte offset from start of buffer.
|
||||
@@ -204,5 +204,5 @@ public interface Buffer {
|
||||
* underlying storage.
|
||||
*/
|
||||
public int putLong(int offset, long v) throws IOException;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,15 +15,30 @@
|
||||
*/
|
||||
package db;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
|
||||
/**
|
||||
* <code>ByteField</code> provides a wrapper for single signed byte data
|
||||
* which is read or written to a Record.
|
||||
*/
|
||||
public class ByteField extends Field {
|
||||
public final class ByteField extends Field {
|
||||
|
||||
/**
|
||||
* Minimum byte field value
|
||||
*/
|
||||
public static final ByteField MIN_VALUE = new ByteField(Byte.MIN_VALUE, true);
|
||||
|
||||
/**
|
||||
* Maximum byte field value
|
||||
*/
|
||||
public static final ByteField MAX_VALUE = new ByteField(Byte.MAX_VALUE, true);
|
||||
|
||||
/**
|
||||
* Instance intended for defining a {@link Table} {@link Schema}
|
||||
*/
|
||||
public static final ByteField INSTANCE = MIN_VALUE;
|
||||
|
||||
private byte value;
|
||||
|
||||
@@ -39,69 +53,57 @@ public class ByteField extends Field {
|
||||
* @param b initial value
|
||||
*/
|
||||
public ByteField(byte b) {
|
||||
this(b, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a byte field with an initial value of b.
|
||||
* @param b initial value
|
||||
* @param immutable true if field value is immutable
|
||||
*/
|
||||
ByteField(byte b, boolean immutable) {
|
||||
super(immutable);
|
||||
value = b;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getByteValue()
|
||||
*/
|
||||
@Override
|
||||
public byte getByteValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#setByteValue(byte)
|
||||
*/
|
||||
@Override
|
||||
public void setByteValue(byte value) {
|
||||
checkImmutable();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#length()
|
||||
*/
|
||||
@Override
|
||||
int length() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#write(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int write(Buffer buf, int offset) throws IOException {
|
||||
return buf.putByte(offset, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#read(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int read(Buffer buf, int offset) throws IOException {
|
||||
checkImmutable();
|
||||
value = buf.getByte(offset);
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#readLength(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int readLength(Buffer buf, int offset) throws IOException {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getFieldType()
|
||||
*/
|
||||
@Override
|
||||
protected byte getFieldType() {
|
||||
byte getFieldType() {
|
||||
return BYTE_TYPE;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Byte: " + Byte.toString(value);
|
||||
@@ -109,12 +111,9 @@ public class ByteField extends Field {
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return Integer.toHexString(value);
|
||||
return "0x" + Integer.toHexString(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof ByteField))
|
||||
@@ -122,9 +121,6 @@ public class ByteField extends Field {
|
||||
return ((ByteField) obj).value == value;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Field o) {
|
||||
ByteField f = (ByteField) o;
|
||||
@@ -135,54 +131,63 @@ public class ByteField extends Field {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#newField(ghidra.framework.store.db.Field)
|
||||
*/
|
||||
@Override
|
||||
public Field newField(Field fieldValue) {
|
||||
if (fieldValue.isVariableLength())
|
||||
throw new AssertException();
|
||||
return new ByteField((byte) fieldValue.getLongValue());
|
||||
int compareTo(DataBuffer buffer, int offset) {
|
||||
byte otherValue = buffer.getByte(offset);
|
||||
if (value == otherValue)
|
||||
return 0;
|
||||
else if (value < otherValue)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#newField()
|
||||
*/
|
||||
@Override
|
||||
public Field newField() {
|
||||
public ByteField copyField() {
|
||||
return new ByteField((byte) getLongValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteField newField() {
|
||||
return new ByteField();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getLongValue()
|
||||
*/
|
||||
@Override
|
||||
public long getLongValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#setLongValue(long)
|
||||
*/
|
||||
@Override
|
||||
public void setLongValue(long value) {
|
||||
this.value = (byte) value;
|
||||
setByteValue((byte) value);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.Field#getBinaryData()
|
||||
*/
|
||||
@Override
|
||||
public byte[] getBinaryData() {
|
||||
return new byte[] { value };
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public void setBinaryData(byte[] bytes) {
|
||||
checkImmutable();
|
||||
if (bytes.length != 1) {
|
||||
throw new IllegalFieldAccessException();
|
||||
}
|
||||
value = bytes[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
ByteField getMinValue() {
|
||||
return MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
ByteField getMaxValue() {
|
||||
return MAX_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* @param unintializedDataSourceOffset uninitialized data source offset which corresponds to
|
||||
* this buffers contents.
|
||||
* @param bufferMgr database buffer manager
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public ChainedBuffer(int size, boolean enableObfuscation, Buffer uninitializedDataSource,
|
||||
int unintializedDataSourceOffset, BufferMgr bufferMgr) throws IOException {
|
||||
@@ -171,7 +171,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* @param size {@literal buffer size (0 < size <= 0x7fffffff)}
|
||||
* @param enableObfuscation true to enable xor-ing of stored data to facilitate data obfuscation.
|
||||
* @param bufferMgr database buffer manager
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public ChainedBuffer(int size, boolean enableObfuscation, BufferMgr bufferMgr)
|
||||
throws IOException {
|
||||
@@ -183,7 +183,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* This method may only be invoked while a database transaction is in progress.
|
||||
* @param size {@literal buffer size (0 < size <= 0x7fffffff)}
|
||||
* @param bufferMgr database buffer manager
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public ChainedBuffer(int size, BufferMgr bufferMgr) throws IOException {
|
||||
this(size, false, null, 0, bufferMgr);
|
||||
@@ -198,7 +198,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* This should not be specified if buffer will be completely filled/initialized.
|
||||
* @param unintializedDataSourceOffset uninitialized data source offset which corresponds to
|
||||
* this buffers contents.
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public ChainedBuffer(BufferMgr bufferMgr, int bufferId, Buffer uninitializedDataSource,
|
||||
int unintializedDataSourceOffset) throws IOException {
|
||||
@@ -238,6 +238,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* Construct an existing chained buffer.
|
||||
* @param bufferMgr database buffer manager
|
||||
* @param bufferId database buffer ID which corresponds to a stored ChainedBuffer
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public ChainedBuffer(BufferMgr bufferMgr, int bufferId) throws IOException {
|
||||
this(bufferMgr, bufferId, null, 0);
|
||||
@@ -249,12 +250,12 @@ public class ChainedBuffer implements Buffer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the XOR value for the specified byteValue which is located at the
|
||||
* Generate the XOR'd value for the specified byteValue which is located at the
|
||||
* specified bufferOffset.
|
||||
* @param bufferOffset offset within a single chained buffer, valid values are in the
|
||||
* range 0 to (dataSpace-1).
|
||||
* @param byteValue
|
||||
* @return
|
||||
* range 0 to (dataSpace-1). This value is used to determine the appropriate XOR mask.
|
||||
* @param byteValue value to be XOR'd against appropriate mask value
|
||||
* @return XOR'd value
|
||||
*/
|
||||
private byte xorMaskByte(int bufferOffset, byte byteValue) {
|
||||
byte maskByte = XOR_MASK_BYTES[bufferOffset % XOR_MASK_BYTES.length];
|
||||
@@ -267,7 +268,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* @param bufferOffset offset within a single chained buffer, valid values are in the
|
||||
* range 0 to (dataSpace-1). The value (bufferOffset+len-1) must be less than dataSpace.
|
||||
* @param len mask length (2, 4, or 8)
|
||||
* @return
|
||||
* @return XOR mask of specified length which corresponds to specified bufferOffset.
|
||||
*/
|
||||
private long getXorMask(int bufferOffset, int len) {
|
||||
long mask = 0;
|
||||
@@ -284,8 +285,9 @@ public class ChainedBuffer implements Buffer {
|
||||
* The same uninitialized read-only dataSource used for a chained buffer should be re-applied
|
||||
* anytime this chained buffer is re-instantiated.
|
||||
*
|
||||
* @param dataSource
|
||||
* @param dataSourceOffset
|
||||
* @param dataSource data source for unitilized bytes
|
||||
* @param dataSourceOffset offset within dataSource which corresponds to first byte of
|
||||
* this chained buffer.
|
||||
*/
|
||||
private void setUnintializedDataSource(Buffer dataSource, int dataSourceOffset) {
|
||||
|
||||
@@ -321,6 +323,7 @@ public class ChainedBuffer implements Buffer {
|
||||
/**
|
||||
* Return the maximum number of buffers consumed by the storage of this DBBuffer object.
|
||||
* The actual number may be less if data has not been written to the entire buffer.
|
||||
* @return total number of buffers consumed by this ChaninedBuffer.
|
||||
*/
|
||||
int getBufferCount() {
|
||||
return dataBufferIdTable.length +
|
||||
@@ -734,7 +737,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* The index buffer provided is always released.
|
||||
* @param indexBuffer the last index buffer.
|
||||
* @return DataBuffer
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
private DataBuffer appendIndexBuffer(DataBuffer indexBuffer) throws IOException {
|
||||
try {
|
||||
@@ -856,6 +859,7 @@ public class ChainedBuffer implements Buffer {
|
||||
|
||||
/**
|
||||
* Delete and release all underlying DataBuffers.
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public synchronized void delete() throws IOException {
|
||||
if (readOnly) {
|
||||
@@ -1115,6 +1119,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* @param startOffset starting offset, inclusive
|
||||
* @param endOffset ending offset, exclusive
|
||||
* @param fillByte byte value
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public synchronized void fill(int startOffset, int endOffset, byte fillByte)
|
||||
throws IOException {
|
||||
@@ -1160,7 +1165,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* @return int actual number of bytes written.
|
||||
* This could be smaller than length if the end of buffer is
|
||||
* encountered while writing data.
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
private int putBytes(int index, int bufferDataOffset, byte[] data, int dataOffset, int length)
|
||||
throws IOException {
|
||||
@@ -1370,9 +1375,6 @@ public class ChainedBuffer implements Buffer {
|
||||
return offset + 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.Buffer#putShort(int, short)
|
||||
*/
|
||||
@Override
|
||||
public synchronized int putShort(int offset, short v) throws IOException {
|
||||
if (readOnly) {
|
||||
@@ -1406,7 +1408,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* Get a data buffer.
|
||||
* @param index index of within buffer chain
|
||||
* @return requested data buffer.
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
private DataBuffer getBuffer(int index) throws IOException {
|
||||
// if databufferIdTable is null, index must be null. let it throw null pointer in this case.
|
||||
@@ -1425,7 +1427,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* Initialize specified DataBuffer which corresponds to the chain index.
|
||||
* @param chainBufferIndex chain buffer index
|
||||
* @param buf newly allocated database buffer
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
private void initializeAllocatedBuffer(int chainBufferIndex, DataBuffer buf)
|
||||
throws IOException {
|
||||
@@ -1455,7 +1457,7 @@ public class ChainedBuffer implements Buffer {
|
||||
* Add a new data buffer as an indexed buffer.
|
||||
* @param index buffer index.
|
||||
* @param buf new data buffer.
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
private void addBuffer(int index, DataBuffer buf) throws IOException {
|
||||
buf.putByte(NODE_TYPE_OFFSET, NodeMgr.CHAINED_BUFFER_DATA_NODE);
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package db;
|
||||
|
||||
import ghidra.util.LongIterator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* <code>DBFieldMap</code> provides a database-backed map of non-unique Field values to long values.
|
||||
*/
|
||||
public class DBFieldMap {
|
||||
|
||||
private static final Class<?>[] fieldClasses = {
|
||||
};
|
||||
|
||||
private static final String[] fieldNames = {
|
||||
};
|
||||
|
||||
private static final int BUFFER_SIZE = 16 * 1024;
|
||||
|
||||
private DBHandle dbh;
|
||||
private Schema schema;
|
||||
private Table indexTable;
|
||||
private Class<? extends Field> fieldClass;
|
||||
|
||||
/**
|
||||
* Construct a new map.
|
||||
* A temporary database is used to provide storage for the map.
|
||||
* @param fieldClass specifies class of Field values to be stored in this map.
|
||||
* @param cacheSizeMB size of data cache in MBytes.
|
||||
*/
|
||||
public DBFieldMap(Class<? extends Field> fieldClass, int cacheSizeMB) {
|
||||
|
||||
if (!Field.class.isAssignableFrom(fieldClass)) {
|
||||
throw new IllegalArgumentException("Field class expected");
|
||||
}
|
||||
|
||||
this.fieldClass = fieldClass;
|
||||
int indexFieldType;
|
||||
try {
|
||||
indexFieldType = Field.INDEX_TYPE_FLAG |
|
||||
fieldClass.newInstance().getFieldType();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Bad Field class: " + e.getMessage());
|
||||
}
|
||||
Field indexKeyField = IndexField.getIndexField((byte)indexFieldType);
|
||||
schema = new Schema(0, indexKeyField.getClass(), "MapKey", fieldClasses, fieldNames);
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
dbh = new DBHandle(BUFFER_SIZE, cacheSizeMB * 1024 * 1024);
|
||||
long txId = dbh.startTransaction();
|
||||
indexTable = dbh.createTable("DBFieldMap", schema);
|
||||
dbh.endTransaction(txId, true);
|
||||
success = true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
if (!success && dbh != null) {
|
||||
dbh.close();
|
||||
dbh = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose all resources associated with this map.
|
||||
* This method should be invoked when the map is no longer needed.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (dbh != null) {
|
||||
dbh.close();
|
||||
dbh = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#finalize()
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified value pair to this map.
|
||||
* If the entry already exists, this method has no affect.
|
||||
* @param fieldValue
|
||||
* @param longValue
|
||||
*/
|
||||
public void addEntry(Field fieldValue, long longValue) {
|
||||
if (!fieldClass.isInstance(fieldValue)) {
|
||||
throw new IllegalArgumentException("Instance of " + fieldClass.getName() + " expected");
|
||||
}
|
||||
IndexField indexField = IndexField.getIndexField(fieldValue, longValue);
|
||||
Record rec = schema.createRecord(indexField);
|
||||
try {
|
||||
long txId = dbh.startTransaction();
|
||||
indexTable.putRecord(rec);
|
||||
dbh.endTransaction(txId, true);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified value pair from this map.
|
||||
* @param fieldValue
|
||||
* @param longValue
|
||||
* @return true if entry exists and was deleted
|
||||
*/
|
||||
public boolean deleteEntry(Field fieldValue, long longValue) {
|
||||
if (!fieldClass.isInstance(fieldValue)) {
|
||||
throw new IllegalArgumentException("Instance of " + fieldClass.getName() + " expected");
|
||||
}
|
||||
IndexField indexField = IndexField.getIndexField(fieldValue, longValue);
|
||||
try {
|
||||
long txId = dbh.startTransaction();
|
||||
boolean success = indexTable.deleteRecord(indexField);
|
||||
dbh.endTransaction(txId, true);
|
||||
return success;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the specified value pair exists within this map.
|
||||
* (This method provided for test purposes).
|
||||
* @param fieldValue
|
||||
* @param longValue
|
||||
* @return
|
||||
*/
|
||||
boolean hasEntry(Field fieldValue, long longValue) {
|
||||
if (!fieldClass.isInstance(fieldValue)) {
|
||||
throw new IllegalArgumentException("Instance of " + fieldClass.getName() + " expected");
|
||||
}
|
||||
IndexField indexField = IndexField.getIndexField(fieldValue, longValue);
|
||||
try {
|
||||
return indexTable.hasRecord(indexField);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public LongIterator iterator() {
|
||||
return new MapLongIterator();
|
||||
}
|
||||
|
||||
private class MapLongIterator implements LongIterator {
|
||||
|
||||
DBFieldIterator indexIterator;
|
||||
|
||||
MapLongIterator() {
|
||||
try {
|
||||
indexIterator = indexTable.fieldKeyIterator();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.util.LongIterator#hasNext()
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
try {
|
||||
return indexIterator.hasNext();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.util.LongIterator#next()
|
||||
*/
|
||||
public long next() {
|
||||
try {
|
||||
IndexField indexField = (IndexField) indexIterator.next();
|
||||
if (indexField == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return indexField.getPrimaryKey();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.util.LongIterator#hasPrevious()
|
||||
*/
|
||||
public boolean hasPrevious() {
|
||||
try {
|
||||
return indexIterator.hasPrevious();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.util.LongIterator#previous()
|
||||
*/
|
||||
public long previous() {
|
||||
try {
|
||||
IndexField indexField = (IndexField) indexIterator.previous();
|
||||
if (indexField == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return indexField.getPrimaryKey();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,11 +17,24 @@ package db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
|
||||
/**
|
||||
* <code>Field</code> is an abstract data wrapper for use with Records.
|
||||
* Note that when comparing two Field instances both must be of the same
|
||||
* class.
|
||||
*/
|
||||
public abstract class Field implements Comparable<Field> {
|
||||
|
||||
public static final Field[] EMPTY_ARRAY = new Field[0];
|
||||
|
||||
/**
|
||||
* 8-bit Field Type Encoding (PPPPFFFF)
|
||||
* where:
|
||||
* FFFF - normal/indexed field type
|
||||
* PPPP - indexed table primary key type (1000b indicates LegacyIndexField)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Field type for ByteField
|
||||
* @see db.ByteField
|
||||
@@ -65,19 +78,56 @@ public abstract class Field implements Comparable<Field> {
|
||||
static final byte BOOLEAN_TYPE = 6;
|
||||
|
||||
/**
|
||||
* Field type flag mask used to isolate flag bits
|
||||
* Field type for 10-byte binary FixedField(10)
|
||||
* @see db.FixedField
|
||||
*/
|
||||
static final byte TYPE_FLAG_MASK = (byte) 0xC0;
|
||||
static final byte FIXED_10_TYPE = 7;
|
||||
|
||||
/**
|
||||
* Field base type mask used to isolate base type
|
||||
* Legacy Index Primary Key Field type for LongField
|
||||
* which was previously a boolean indicator for an index
|
||||
* field with assumed long primary key.
|
||||
* (see {@link LegacyIndexField})
|
||||
*/
|
||||
static final byte BASE_TYPE_MASK = (byte) 0x3F;
|
||||
static final byte LEGACY_INDEX_LONG_TYPE = 8;
|
||||
|
||||
/**
|
||||
* Field type flag bit shared by all Index type fields
|
||||
* Field base type mask
|
||||
*/
|
||||
static final byte INDEX_TYPE_FLAG = (byte) 0x80;
|
||||
static final byte FIELD_TYPE_MASK = (byte) 0x0F;
|
||||
|
||||
/**
|
||||
* Field index primary key type mask
|
||||
*/
|
||||
static final byte INDEX_PRIMARY_KEY_TYPE_MASK = (byte) ~FIELD_TYPE_MASK;
|
||||
|
||||
/**
|
||||
* Index Primary Key Field Type Shift
|
||||
*/
|
||||
static final int INDEX_FIELD_TYPE_SHIFT = 4;
|
||||
|
||||
private final boolean immutable;
|
||||
|
||||
/**
|
||||
* Abstract Field Constructor for a mutable instance
|
||||
*/
|
||||
Field() {
|
||||
immutable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Field Constructor
|
||||
* @param immutable true if field value is immutable
|
||||
*/
|
||||
Field(boolean immutable) {
|
||||
this.immutable = immutable;
|
||||
}
|
||||
|
||||
void checkImmutable() {
|
||||
if (immutable) {
|
||||
throw new IllegalFieldAccessException("immutable field instance");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field as a long value.
|
||||
@@ -191,10 +241,11 @@ public abstract class Field implements Comparable<Field> {
|
||||
* Set data from binary byte array.
|
||||
* All variable-length fields must implement this method.
|
||||
* @param bytes field data
|
||||
* @throws IllegalFieldAccessException if error occurs while reading bytes
|
||||
* into field which will generally be caused by the incorrect number of
|
||||
* bytes provided to a fixed-length field.
|
||||
*/
|
||||
public void setBinaryData(byte[] bytes) {
|
||||
throw new IllegalFieldAccessException();
|
||||
}
|
||||
abstract public void setBinaryData(byte[] bytes);
|
||||
|
||||
/**
|
||||
* Get field as a String value.
|
||||
@@ -219,10 +270,10 @@ public abstract class Field implements Comparable<Field> {
|
||||
/**
|
||||
* Truncate a variable length field to the specified length.
|
||||
* If current length is shorterm, this method has no affect.
|
||||
* @param length
|
||||
* @param length truncated length
|
||||
*/
|
||||
void truncate(int length) {
|
||||
throw new IllegalFieldAccessException();
|
||||
throw new UnsupportedOperationException("Field may not be truncated");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,22 +284,31 @@ public abstract class Field implements Comparable<Field> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance of this field type.
|
||||
* @param fieldValue initial field value.
|
||||
* @return long
|
||||
* Determine if specified field is same type as this field
|
||||
* @param field a Field instance
|
||||
* @return true if field is same type as this field
|
||||
*/
|
||||
public abstract Field newField(Field fieldValue);
|
||||
public boolean isSameType(Field field) {
|
||||
return field != null && field.getClass() == getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance of this field with the same value.
|
||||
* @return new field instance with same value
|
||||
*/
|
||||
public abstract Field copyField();
|
||||
|
||||
/**
|
||||
* Create new instance of this field type.
|
||||
* @return long
|
||||
* @return new field instance with undefined initial value
|
||||
*/
|
||||
public abstract Field newField();
|
||||
|
||||
/**
|
||||
* Return Field instance type as an integer value
|
||||
* Return Field instance type as an integer value.
|
||||
* @return encoded field type
|
||||
*/
|
||||
protected abstract byte getFieldType();
|
||||
abstract byte getFieldType();
|
||||
|
||||
/**
|
||||
* Write the field to buf at the specified offset. When writing variable length
|
||||
@@ -292,40 +352,73 @@ public abstract class Field implements Comparable<Field> {
|
||||
*/
|
||||
abstract int length();
|
||||
|
||||
/*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public abstract boolean equals(Object obj);
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
/**
|
||||
* Get field value as a formatted string
|
||||
* @return field value string
|
||||
*/
|
||||
public abstract String getValueAsString();
|
||||
|
||||
/**
|
||||
* Get minimum field value.
|
||||
*
|
||||
* Supported for fixed-length fields only.
|
||||
* @return minimum value
|
||||
* @throws UnsupportedOperationException if field is not fixed-length
|
||||
*/
|
||||
abstract Field getMinValue();
|
||||
|
||||
/**
|
||||
* Get maximum field value.
|
||||
*
|
||||
* Supported for fixed-length fields only.
|
||||
* @return maximum value
|
||||
* @throws UnsupportedOperationException if field is not fixed-length
|
||||
*/
|
||||
abstract Field getMaxValue();
|
||||
|
||||
/**
|
||||
* Performs a fast in-place comparison of this field value with another
|
||||
* field value stored within the specified buffer at the the specified offset.
|
||||
* @param buffer data buffer
|
||||
* @param offset field value offset within buffer
|
||||
* @return comparison value, zero if equal, -1 if this field has a value
|
||||
* less than the stored field, or +1 if this field has a value greater than
|
||||
* the stored field located at keyIndex.
|
||||
*/
|
||||
abstract int compareTo(DataBuffer buffer, int offset);
|
||||
|
||||
/**
|
||||
* Get the field associated with the specified type value.
|
||||
* @param fieldType encoded Field type
|
||||
* @return Field
|
||||
* @param fieldType field type index
|
||||
* @return Field field instance which corresponds to the specified fieldType
|
||||
* @throws UnsupportedFieldException if unsupported fieldType specified
|
||||
*/
|
||||
static Field getField(byte fieldType) throws UnsupportedFieldException {
|
||||
if ((fieldType & INDEX_TYPE_FLAG) == 0) {
|
||||
switch (fieldType & BASE_TYPE_MASK) {
|
||||
|
||||
if ((fieldType & INDEX_PRIMARY_KEY_TYPE_MASK) == 0) {
|
||||
switch (fieldType & FIELD_TYPE_MASK) {
|
||||
case LONG_TYPE:
|
||||
return new LongField();
|
||||
return LongField.INSTANCE;
|
||||
case INT_TYPE:
|
||||
return new IntField();
|
||||
return IntField.INSTANCE;
|
||||
case STRING_TYPE:
|
||||
return new StringField();
|
||||
return StringField.INSTANCE;
|
||||
case SHORT_TYPE:
|
||||
return new ShortField();
|
||||
return ShortField.INSTANCE;
|
||||
case BYTE_TYPE:
|
||||
return new ByteField();
|
||||
return ByteField.INSTANCE;
|
||||
case BOOLEAN_TYPE:
|
||||
return new BooleanField();
|
||||
return BooleanField.INSTANCE;
|
||||
case BINARY_OBJ_TYPE:
|
||||
return new BinaryField();
|
||||
return BinaryField.INSTANCE;
|
||||
case FIXED_10_TYPE:
|
||||
return FixedField10.INSTANCE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -340,4 +433,53 @@ public abstract class Field implements Comparable<Field> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type index value of the FixedField type which corresponds
|
||||
* to the specified fixed-length;
|
||||
* @param fixedLength fixed length
|
||||
* @return FixedLength field type index
|
||||
*/
|
||||
static byte getFixedType(int fixedLength) {
|
||||
if (fixedLength == 10) {
|
||||
return FIXED_10_TYPE;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported fixed-length binary type size: " + fixedLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a fixed-length field of the specified size
|
||||
* @param size fixed-field length (supported sizes: 1, 4, 8, 10)
|
||||
* @return fixed field instance
|
||||
* @throws IllegalArgumentException if unsupported fixed field length
|
||||
*/
|
||||
static Field getFixedField(int size) {
|
||||
switch (size) {
|
||||
case 1:
|
||||
return new ByteField();
|
||||
case 4:
|
||||
return new IntField();
|
||||
case 8:
|
||||
return new LongField();
|
||||
case 10:
|
||||
return new FixedField10();
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported fixed-field length: " + size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a specified field instance may be indexed
|
||||
* @param field field to be checked
|
||||
* @return true if field can be indexed
|
||||
*/
|
||||
public static boolean canIndex(Field field) {
|
||||
if (field == null) {
|
||||
return false;
|
||||
}
|
||||
if (field instanceof IndexField) {
|
||||
return false;
|
||||
}
|
||||
return !field.isSameType(BooleanField.INSTANCE) && !field.isSameType(ByteField.INSTANCE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <code>FieldKeyInteriorNode</code> defines a common interface for {@link FieldKeyNode}
|
||||
* implementations which are also an {@link InteriorNode}.
|
||||
*/
|
||||
public interface FieldKeyInteriorNode extends InteriorNode, FieldKeyNode {
|
||||
|
||||
/**
|
||||
* Callback method for when a child node's leftmost key changes.
|
||||
* @param oldKey previous leftmost key.
|
||||
* @param newKey new leftmost key.
|
||||
* @param childNode child node containing oldKey (null if not a VarKeyNode)
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
void keyChanged(Field oldKey, Field newKey, FieldKeyNode childNode) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <code>FieldKeyNode</code> defines a common interface for {@link BTreeNode}
|
||||
* implementations which utilize a {@link Field} key.
|
||||
*/
|
||||
interface FieldKeyNode extends BTreeNode {
|
||||
|
||||
/**
|
||||
* @return the parent node or null if this is the root
|
||||
*/
|
||||
@Override
|
||||
public FieldKeyInteriorNode getParent();
|
||||
|
||||
/**
|
||||
* Get the leaf node which contains the specified key.
|
||||
* @param key key value
|
||||
* @return leaf node
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
public FieldKeyRecordNode getLeafNode(Field key) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the left-most leaf node within the tree.
|
||||
* @return left-most leaf node.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
abstract FieldKeyRecordNode getLeftmostLeafNode() throws IOException;
|
||||
|
||||
/**
|
||||
* Get the right-most leaf node within the tree.
|
||||
* @return right-most leaf node.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
abstract FieldKeyRecordNode getRightmostLeafNode() throws IOException;
|
||||
|
||||
/**
|
||||
* Performs a fast in-place key comparison of the specified key
|
||||
* value with a key stored within this node at the specified keyIndex.
|
||||
* @param k key value to be compared
|
||||
* @param keyIndex key index to another key within this node's buffer
|
||||
* @return comparison value, zero if equal, -1 if k has a value less than
|
||||
* the store key, or +1 if k has a value greater than the stored key located
|
||||
* at keyIndex.
|
||||
*/
|
||||
abstract int compareKeyField(Field k, int keyIndex);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <code>FieldKeyRecordNode</code> defines a common interface for {@link FieldKeyNode}
|
||||
* implementations which are also a {@link RecordNode} (i.e., leaf node).
|
||||
*/
|
||||
interface FieldKeyRecordNode extends RecordNode, FieldKeyNode {
|
||||
|
||||
/**
|
||||
* Get the record located at the specified index.
|
||||
* @param schema record data schema
|
||||
* @param index key index
|
||||
* @return Record
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
Record getRecord(Schema schema, int index) throws IOException;
|
||||
|
||||
/**
|
||||
* Insert or Update a record.
|
||||
* @param record data record with long key
|
||||
* @param table table which will be notified when record is inserted or updated.
|
||||
* @return root node which may have changed.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
FieldKeyNode putRecord(Record record, Table table) throws IOException;
|
||||
|
||||
/**
|
||||
* Remove the record identified by index.
|
||||
* This will never be the last record within the node.
|
||||
* @param index record index
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
void remove(int index) throws IOException;
|
||||
|
||||
/**
|
||||
* Determine if this record node has a right sibling.
|
||||
* @return true if right sibling exists
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
boolean hasNextLeaf() throws IOException;
|
||||
|
||||
/**
|
||||
* Get this leaf node's right sibling
|
||||
* @return this leaf node's right sibling or null if right sibling does not exist.
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
FieldKeyRecordNode getNextLeaf() throws IOException;
|
||||
|
||||
/**
|
||||
* Determine if this record node has a left sibling.
|
||||
* @return true if left sibling exists
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
boolean hasPreviousLeaf() throws IOException;
|
||||
|
||||
/**
|
||||
* Get this leaf node's left sibling
|
||||
* @return this leaf node's left sibling or null if left sibling does not exist.
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
FieldKeyRecordNode getPreviousLeaf() throws IOException;
|
||||
|
||||
/**
|
||||
* Remove this leaf from the tree.
|
||||
* @return root node which may have changed.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
FieldKeyNode removeLeaf() throws IOException;
|
||||
|
||||
/**
|
||||
* Delete the record identified by the specified key.
|
||||
* @param key record key
|
||||
* @param table table which will be notified when record is deleted.
|
||||
* @return root node which may have changed.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
FieldKeyNode deleteRecord(Field key, Table table) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the record with the minimum key value which is greater than or equal
|
||||
* to the specified key.
|
||||
* @param key search key
|
||||
* @param schema record data schema
|
||||
* @return Record requested or null if record not found.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
Record getRecordAtOrAfter(Field key, Schema schema) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the record with the maximum key value which is less than or equal
|
||||
* to the specified key.
|
||||
* @param key search key
|
||||
* @param schema record data schema
|
||||
* @return Record requested or null if record not found.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
Record getRecordAtOrBefore(Field key, Schema schema) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the record with the minimum key value which is greater than
|
||||
* the specified key.
|
||||
* @param key search key
|
||||
* @param schema record data schema
|
||||
* @return Record requested or null if record not found.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
Record getRecordAfter(Field key, Schema schema) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the record with the maximum key value which is less than
|
||||
* the specified key.
|
||||
* @param key search key
|
||||
* @param schema record data schema
|
||||
* @return Record requested or null if record not found.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
Record getRecordBefore(Field key, Schema schema) throws IOException;
|
||||
|
||||
/**
|
||||
* Get the record identified by the specified key.
|
||||
* @param key search key
|
||||
* @param schema record data schema
|
||||
* @return Record requested or null if record not found.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
Record getRecord(Field key, Schema schema) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
/**
|
||||
* <code>FixedField</code> provides an abstract implementation of a fixed-length
|
||||
* binary field.
|
||||
*/
|
||||
public abstract class FixedField extends BinaryField {
|
||||
|
||||
/**
|
||||
* Construct a fixed-length field
|
||||
* @param data initial value
|
||||
* @param immutable true if field value is immutable
|
||||
*/
|
||||
FixedField(byte[] data, boolean immutable) {
|
||||
super(data, immutable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isVariableLength() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void truncate(int length) {
|
||||
throw new UnsupportedOperationException("Field may not be truncated");
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract FixedField copyField();
|
||||
|
||||
@Override
|
||||
public abstract FixedField newField();
|
||||
|
||||
@Override
|
||||
abstract FixedField getMinValue();
|
||||
|
||||
@Override
|
||||
abstract FixedField getMaxValue();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
import generic.util.UnsignedDataUtils;
|
||||
import ghidra.util.BigEndianDataConverter;
|
||||
|
||||
/**
|
||||
* <code>FixedField10</code> is a 10-byte fixed-length binary field.
|
||||
*/
|
||||
public class FixedField10 extends FixedField {
|
||||
|
||||
/**
|
||||
* Minimum long field value
|
||||
*/
|
||||
public static FixedField10 MIN_VALUE = new FixedField10(0L, (short) 0, true);
|
||||
|
||||
/**
|
||||
* Maximum long field value
|
||||
*/
|
||||
public static FixedField10 MAX_VALUE = new FixedField10(-1L, (short) -1, true);
|
||||
|
||||
/**
|
||||
* Instance intended for defining a {@link Table} {@link Schema}
|
||||
*/
|
||||
@SuppressWarnings("hiding")
|
||||
public static final FixedField10 INSTANCE = MIN_VALUE;
|
||||
|
||||
// This implementation uses both a data byte array and short+long variables
|
||||
// for data storage. While the short+long is always available, the data
|
||||
// byte array is only set when needed or supplied during construction.
|
||||
// The use of the short+long is done to speed-up comparison with other
|
||||
// FixedField10 instances or directly from a DataBuffer.
|
||||
private short lo2;
|
||||
private long hi8;
|
||||
|
||||
/**
|
||||
* Construct a 10-byte fixed-length field with an initial value of 0.
|
||||
*/
|
||||
public FixedField10() {
|
||||
super(null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a 10-byte fixed-length field with an initial value of data.
|
||||
* @param data initial 10-byte binary value
|
||||
* @throws IllegalArgumentException thrown if data is not 10-bytes in length
|
||||
*/
|
||||
public FixedField10(byte[] data) {
|
||||
this(data, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a 10-byte fixed-length binary field with an initial value of data.
|
||||
* @param data initial 10-byte binary value
|
||||
* @param immutable true if field value is immutable
|
||||
* @throws IllegalArgumentException thrown if data is not 10-bytes in length
|
||||
*/
|
||||
public FixedField10(byte[] data, boolean immutable) {
|
||||
super(null, immutable);
|
||||
setBinaryData(data);
|
||||
}
|
||||
|
||||
FixedField10(long hi8, short lo2, boolean immutable) {
|
||||
super(null, immutable);
|
||||
this.hi8 = hi8;
|
||||
this.lo2 = lo2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Field o) {
|
||||
if (!(o instanceof FixedField10)) {
|
||||
throw new UnsupportedOperationException("may only compare similar Field types");
|
||||
}
|
||||
FixedField10 f = (FixedField10) o;
|
||||
if (hi8 != f.hi8) {
|
||||
return UnsignedDataUtils.unsignedLessThan(hi8, f.hi8) ? -1 : 1;
|
||||
}
|
||||
if (lo2 != f.lo2) {
|
||||
return UnsignedDataUtils.unsignedLessThan(lo2, f.lo2) ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
int compareTo(DataBuffer buffer, int offset) {
|
||||
long otherHi8 = buffer.getLong(offset);
|
||||
if (hi8 != otherHi8) {
|
||||
return UnsignedDataUtils.unsignedLessThan(hi8, otherHi8) ? -1 : 1;
|
||||
}
|
||||
short otherLo2 = buffer.getShort(offset + 8);
|
||||
if (lo2 != otherLo2) {
|
||||
return UnsignedDataUtils.unsignedLessThan(lo2, otherLo2) ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FixedField copyField() {
|
||||
return new FixedField10(hi8, lo2, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FixedField newField() {
|
||||
return new FixedField10();
|
||||
}
|
||||
|
||||
@Override
|
||||
FixedField getMinValue() {
|
||||
return MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
FixedField getMaxValue() {
|
||||
return MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBinaryData() {
|
||||
if (data != null) {
|
||||
return data;
|
||||
}
|
||||
data = new byte[10];
|
||||
BigEndianDataConverter.INSTANCE.putLong(data, 0, hi8);
|
||||
BigEndianDataConverter.INSTANCE.putShort(data, 8, lo2);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryData(byte[] data) {
|
||||
if (data.length != 10) {
|
||||
throw new IllegalArgumentException("Invalid FixedField10 length: " + data.length);
|
||||
}
|
||||
this.data = data;
|
||||
hi8 = BigEndianDataConverter.INSTANCE.getLong(data, 0);
|
||||
lo2 = BigEndianDataConverter.INSTANCE.getShort(data, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
byte getFieldType() {
|
||||
return FIXED_10_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
int write(Buffer buf, int offset) throws IOException {
|
||||
if (data != null) {
|
||||
return buf.put(offset, data);
|
||||
}
|
||||
offset = buf.putLong(offset, hi8);
|
||||
return buf.putShort(offset, lo2);
|
||||
}
|
||||
|
||||
@Override
|
||||
int read(Buffer buf, int offset) throws IOException {
|
||||
checkImmutable();
|
||||
data = null; // be lazy
|
||||
hi8 = buf.getLong(offset);
|
||||
lo2 = buf.getShort(offset + 8);
|
||||
return offset + 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
int readLength(Buffer buf, int offset) throws IOException {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
int length() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = (int) (hi8 ^ (hi8 >>> 32));
|
||||
result = prime * result + lo2;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
FixedField10 other = (FixedField10) obj;
|
||||
if (hi8 != other.hi8)
|
||||
return false;
|
||||
if (lo2 != other.lo2)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return "{" + BinaryField.getValueAsString(getBinaryData()) + "}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,328 +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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* The <code>FixedIndexTable</code> provides a secondary index on a fixed-length table column
|
||||
* (e.g., IntField, LongField, etc.). For each unique secondary index value, an IndexBuffer is
|
||||
* stored within an underlying index Table record. The secondary index value is used as the long
|
||||
* key to access this record. Within a single IndexBuffer is stored all primary keys which
|
||||
* correspond to an index value.
|
||||
*/
|
||||
class FixedIndexTable extends IndexTable {
|
||||
|
||||
private static final Class<?>[] fieldClasses = { BinaryField.class, // index data
|
||||
};
|
||||
|
||||
private static final String[] fieldNames = { "IndexBuffer" };
|
||||
|
||||
private static Schema indexSchema = new Schema(0, "IndexKey", fieldClasses, fieldNames);
|
||||
|
||||
/**
|
||||
* Construct a new secondary index which is based upon a field within the
|
||||
* primary table specified by name.
|
||||
* @param primaryTable primary table.
|
||||
* @param colIndex identifies the indexed column within the primary table.
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
FixedIndexTable(Table primaryTable, int colIndex) throws IOException {
|
||||
this(primaryTable, primaryTable.getDBHandle().getMasterTable().createTableRecord(
|
||||
primaryTable.getName(), indexSchema, colIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new or existing secondary index. An existing index must have
|
||||
* its root ID specified within the tableRecord.
|
||||
* @param primaryTable primary table.
|
||||
* @param indexTableRecord specifies the index parameters.
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
FixedIndexTable(Table primaryTable, TableRecord indexTableRecord) throws IOException {
|
||||
super(primaryTable, indexTableRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all primary keys which correspond to the specified indexed field
|
||||
* value.
|
||||
* @param indexValue the field value to search for.
|
||||
* @return list of primary keys
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
@Override
|
||||
long[] findPrimaryKeys(Field indexValue) throws IOException {
|
||||
if (!indexValue.getClass().equals(fieldType.getClass())) {
|
||||
throw new IllegalArgumentException("Incorrect indexed field type");
|
||||
}
|
||||
Record indexRecord = indexTable.getRecord(indexValue.getLongValue());
|
||||
if (indexRecord == null) {
|
||||
return emptyKeyArray;
|
||||
}
|
||||
IndexBuffer indexBuffer = new IndexBuffer(indexValue, indexRecord.getBinaryData(0));
|
||||
return indexBuffer.getPrimaryKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of primary keys which correspond to the specified indexed field
|
||||
* value.
|
||||
* @param indexValue the field value to search for.
|
||||
* @return key count
|
||||
*/
|
||||
@Override
|
||||
int getKeyCount(Field indexValue) throws IOException {
|
||||
if (!indexValue.getClass().equals(fieldType.getClass())) {
|
||||
throw new IllegalArgumentException("Incorrect indexed field type");
|
||||
}
|
||||
Record indexRecord = indexTable.getRecord(indexValue.getLongValue());
|
||||
if (indexRecord == null) {
|
||||
return 0;
|
||||
}
|
||||
IndexBuffer indexBuffer = new IndexBuffer(indexValue, indexRecord.getBinaryData(0));
|
||||
return indexBuffer.keyCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.IndexTable#addEntry(ghidra.framework.store.db.Record)
|
||||
*/
|
||||
@Override
|
||||
void addEntry(Record record) throws IOException {
|
||||
Field indexField = record.getField(colIndex);
|
||||
long secondaryKey = indexField.getLongValue();
|
||||
Record indexRecord = indexTable.getRecord(secondaryKey);
|
||||
if (indexRecord == null) {
|
||||
indexRecord = indexSchema.createRecord(secondaryKey);
|
||||
}
|
||||
IndexBuffer indexBuffer = new IndexBuffer(indexField, indexRecord.getBinaryData(0));
|
||||
indexBuffer.addEntry(record.getKey());
|
||||
indexRecord.setBinaryData(0, indexBuffer.getData());
|
||||
indexTable.putRecord(indexRecord);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.IndexTable#deleteEntry(ghidra.framework.store.db.Record)
|
||||
*/
|
||||
@Override
|
||||
void deleteEntry(Record record) throws IOException {
|
||||
Field indexField = record.getField(colIndex);
|
||||
long secondaryKey = indexField.getLongValue();
|
||||
Record indexRecord = indexTable.getRecord(secondaryKey);
|
||||
if (indexRecord != null) {
|
||||
IndexBuffer indexBuffer = new IndexBuffer(indexField, indexRecord.getBinaryData(0));
|
||||
indexBuffer.deleteEntry(record.getKey());
|
||||
byte[] data = indexBuffer.getData();
|
||||
if (data == null) {
|
||||
indexTable.deleteRecord(secondaryKey);
|
||||
}
|
||||
else {
|
||||
indexRecord.setBinaryData(0, data);
|
||||
indexTable.putRecord(indexRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index buffer associated with the specified index key
|
||||
* @param indexKey index key
|
||||
* @return index buffer or null if not found
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
private IndexBuffer getIndexBuffer(Field indexKey) throws IOException {
|
||||
Record indexRec = indexTable.getRecord(indexKey.getLongValue());
|
||||
return indexRec != null ? new IndexBuffer(indexKey, indexRec.getBinaryData(0)) : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.IndexTable#indexIterator()
|
||||
*/
|
||||
@Override
|
||||
DBFieldIterator indexIterator() throws IOException {
|
||||
return new IndexLongIterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.IndexTable#indexIterator(ghidra.framework.store.db.Field, ghidra.framework.store.db.Field, boolean)
|
||||
*/
|
||||
@Override
|
||||
DBFieldIterator indexIterator(Field minField, Field maxField, boolean atMin)
|
||||
throws IOException {
|
||||
long min = minField != null ? minField.getLongValue() : Long.MIN_VALUE;
|
||||
long max = maxField != null ? maxField.getLongValue() : Long.MAX_VALUE;
|
||||
return new IndexLongIterator(min, max, atMin);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see db.IndexTable#indexIterator(db.Field, db.Field, db.Field, boolean)
|
||||
*/
|
||||
@Override
|
||||
DBFieldIterator indexIterator(Field minField, Field maxField, Field startField, boolean before)
|
||||
throws IOException {
|
||||
if (startField == null) {
|
||||
throw new IllegalArgumentException("starting index value required");
|
||||
}
|
||||
long min = minField != null ? minField.getLongValue() : Long.MIN_VALUE;
|
||||
long max = maxField != null ? maxField.getLongValue() : Long.MAX_VALUE;
|
||||
return new IndexLongIterator(min, max, startField.getLongValue(), before);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over index field values within a specified range.
|
||||
*/
|
||||
class IndexLongIterator implements DBFieldIterator {
|
||||
|
||||
private Field lastKey;
|
||||
private Field keyField;
|
||||
private DBLongIterator indexIterator;
|
||||
private boolean hasNext = false;
|
||||
private boolean hasPrev = false;
|
||||
|
||||
/**
|
||||
* Construct an index field iterator starting with the minimum index value.
|
||||
*/
|
||||
IndexLongIterator() throws IOException {
|
||||
indexIterator = indexTable.longKeyIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an index field iterator. The iterator is positioned at index
|
||||
* value identified by startValue.
|
||||
* @param startValue minimum index value or null if no minimum
|
||||
* @param endValue maximum index value or null if no maximum
|
||||
* @param atStart if true initial position is before startValue, else position
|
||||
* is after endValue
|
||||
* @throws IOException
|
||||
*/
|
||||
IndexLongIterator(long minValue, long maxValue, boolean atMin) throws IOException {
|
||||
|
||||
long start = atMin ? minValue : maxValue;
|
||||
indexIterator = indexTable.longKeyIterator(minValue, maxValue, start);
|
||||
|
||||
if (indexIterator.hasNext()) {
|
||||
indexIterator.next();
|
||||
if (atMin) {
|
||||
indexIterator.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param min
|
||||
* @param max
|
||||
* @param longValue
|
||||
* @param before
|
||||
*/
|
||||
public IndexLongIterator(long minValue, long maxValue, long start, boolean before)
|
||||
throws IOException {
|
||||
|
||||
indexIterator = indexTable.longKeyIterator(minValue, maxValue, start);
|
||||
|
||||
if (indexIterator.hasNext()) {
|
||||
long val = indexIterator.next();
|
||||
if (before || val != start) {
|
||||
indexIterator.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() throws IOException {
|
||||
if (hasNext) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
long key = indexIterator.next();
|
||||
keyField = fieldType.newField();
|
||||
keyField.setLongValue(key);
|
||||
hasNext = true;
|
||||
hasPrev = false;
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() throws IOException {
|
||||
if (hasPrev) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
long key = indexIterator.previous();
|
||||
keyField = fieldType.newField();
|
||||
keyField.setLongValue(key);
|
||||
hasNext = false;
|
||||
hasPrev = true;
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field next() throws IOException {
|
||||
if (hasNext || hasNext()) {
|
||||
hasNext = false;
|
||||
hasPrev = true;
|
||||
lastKey = keyField;
|
||||
return keyField;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field previous() throws IOException {
|
||||
if (hasPrev || hasPrevious()) {
|
||||
hasNext = true;
|
||||
hasPrev = false;
|
||||
lastKey = keyField;
|
||||
return keyField;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all primary records which have the current
|
||||
* index value (lastKey).
|
||||
* @see db.DBFieldIterator#delete()
|
||||
*/
|
||||
@Override
|
||||
public boolean delete() throws IOException {
|
||||
if (lastKey == null) {
|
||||
return false;
|
||||
}
|
||||
synchronized (db) {
|
||||
IndexBuffer indexBuf = getIndexBuffer(lastKey);
|
||||
if (indexBuf != null) {
|
||||
long[] keys = indexBuf.getPrimaryKeys();
|
||||
for (long key : keys) {
|
||||
primaryTable.deleteRecord(key);
|
||||
}
|
||||
// The following does not actually delete the index record since it
|
||||
// should already have been removed with the removal of all associated
|
||||
// primary records. Invoking this method allows the iterator to
|
||||
// recover from the index table change.
|
||||
indexIterator.delete();
|
||||
}
|
||||
lastKey = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* <code>FixedKeyFixedRecNode</code> is an implementation of a BTree leaf node
|
||||
* which utilizes fixed-length key values and stores fixed-length records.
|
||||
* <p>
|
||||
* This type of node has the following layout within a single DataBuffer
|
||||
* (field size in bytes, where 'L' is the fixed length of the fixed-length
|
||||
* key as specified by key type in associated Schema):
|
||||
* <pre>
|
||||
* | NodeType(1) | KeyCount(4) | PrevLeafId(4) | NextLeafId(4) | Key0(L) | Rec0 | ...
|
||||
*
|
||||
* | KeyN(L) | RecN |
|
||||
* </pre>
|
||||
*/
|
||||
class FixedKeyFixedRecNode extends FixedKeyRecordNode {
|
||||
|
||||
private static final int HEADER_SIZE = RECORD_LEAF_HEADER_SIZE;
|
||||
|
||||
private static final int ENTRY_BASE_OFFSET = HEADER_SIZE;
|
||||
|
||||
private static final int[] EMPTY_ID_LIST = new int[0];
|
||||
|
||||
private int entrySize;
|
||||
private int recordLength;
|
||||
|
||||
/**
|
||||
* Construct an existing fixed-length key fixed-length record leaf node.
|
||||
* @param nodeMgr table node manager instance
|
||||
* @param buf node buffer
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
FixedKeyFixedRecNode(NodeMgr nodeMgr, DataBuffer buf) throws IOException {
|
||||
super(nodeMgr, buf);
|
||||
this.recordLength = nodeMgr.getTableSchema().getFixedLength();
|
||||
entrySize = keySize + recordLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new fixed-length key fixed-length record leaf node.
|
||||
* @param nodeMgr table node manager instance
|
||||
* @param prevLeafId node buffer id for previous leaf ( < 0: no leaf)
|
||||
* @param nextLeafId node buffer id for next leaf ( < 0 : no leaf)
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
FixedKeyFixedRecNode(NodeMgr nodeMgr, int prevLeafId, int nextLeafId) throws IOException {
|
||||
super(nodeMgr, NodeMgr.FIXEDKEY_FIXED_REC_NODE, prevLeafId, nextLeafId);
|
||||
this.recordLength = nodeMgr.getTableSchema().getFixedLength();
|
||||
entrySize = keySize + recordLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
FixedKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
|
||||
return new FixedKeyFixedRecNode(nodeMgr, prevLeafId, nextLeafId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyOffset(int index) {
|
||||
return ENTRY_BASE_OFFSET + (index * entrySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the record offset within the buffer
|
||||
* @param index key index
|
||||
* @return record offset
|
||||
*/
|
||||
@Override
|
||||
public int getRecordOffset(int index) {
|
||||
return ENTRY_BASE_OFFSET + (index * entrySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift all records by one starting with index to the end.
|
||||
* @param index the smaller key index (0 <= index1)
|
||||
* @param rightShift shift right by one record if true, else shift left by
|
||||
* one record.
|
||||
*/
|
||||
private void shiftRecords(int index, boolean rightShift) {
|
||||
|
||||
// No movement needed for appended record
|
||||
if (index == keyCount)
|
||||
return;
|
||||
|
||||
// Determine block to be moved
|
||||
int start = getRecordOffset(index);
|
||||
int end = getRecordOffset(keyCount);
|
||||
int len = end - start;
|
||||
|
||||
// Move record data
|
||||
int offset = start + (rightShift ? entrySize : -entrySize);
|
||||
buffer.move(start, offset, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(int index) {
|
||||
|
||||
if (index < 0 || index >= keyCount)
|
||||
throw new AssertException();
|
||||
|
||||
shiftRecords(index + 1, false);
|
||||
setKeyCount(keyCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean insertRecord(int index, Record record) throws IOException {
|
||||
|
||||
// Check for use of indirect chained record node(s)
|
||||
// int len = record.length();
|
||||
|
||||
if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize))
|
||||
return false; // insufficient space for record storage
|
||||
|
||||
// Make room for new record
|
||||
shiftRecords(index, true);
|
||||
|
||||
// Store new record
|
||||
int offset = getRecordOffset(index);
|
||||
record.getKeyField().write(buffer, offset);
|
||||
record.write(buffer, offset + keySize);
|
||||
setKeyCount(keyCount + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
FixedKeyNode updateRecord(int index, Record record) throws IOException {
|
||||
int offset = getRecordOffset(index) + keySize;
|
||||
record.write(buffer, offset);
|
||||
return getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record getRecord(Field key, Schema schema) throws IOException {
|
||||
int index = getKeyIndex(key);
|
||||
if (index < 0)
|
||||
return null;
|
||||
Record record = schema.createRecord(key);
|
||||
record.read(buffer, getRecordOffset(index) + keySize);
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record getRecord(Schema schema, int index) throws IOException {
|
||||
Field key = getKeyField(index);
|
||||
Record record = schema.createRecord(key);
|
||||
record.read(buffer, getRecordOffset(index) + keySize);
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
void splitData(FixedKeyRecordNode newRightLeaf) {
|
||||
|
||||
FixedKeyFixedRecNode rightNode = (FixedKeyFixedRecNode) newRightLeaf;
|
||||
|
||||
int splitIndex = keyCount / 2;
|
||||
int count = keyCount - splitIndex;
|
||||
int start = getRecordOffset(splitIndex); // start of block to be moved
|
||||
int end = getRecordOffset(keyCount); // end of block to be moved
|
||||
int splitLen = end - start; // length of block to be moved
|
||||
|
||||
// Copy data to new leaf node
|
||||
rightNode.buffer.copy(ENTRY_BASE_OFFSET, buffer, start, splitLen);
|
||||
|
||||
// Adjust key counts
|
||||
setKeyCount(keyCount - count);
|
||||
rightNode.setKeyCount(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
nodeMgr.deleteNode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getBufferReferences() {
|
||||
return EMPTY_ID_LIST;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* <code>FixedKeyNode</code> is an abstract implementation of a BTree node
|
||||
* which utilizes fixed-length key values.
|
||||
* <pre>
|
||||
* | NodeType(1) | KeyCount(4) | ...
|
||||
* </pre>
|
||||
*/
|
||||
abstract class FixedKeyNode implements FieldKeyNode {
|
||||
|
||||
private static final int KEY_COUNT_SIZE = 4;
|
||||
private static final int KEY_COUNT_OFFSET = NodeMgr.NODE_HEADER_SIZE;
|
||||
|
||||
static final int FIXEDKEY_NODE_HEADER_SIZE = NodeMgr.NODE_HEADER_SIZE + KEY_COUNT_SIZE;
|
||||
|
||||
protected final Field keyType;
|
||||
protected final int keySize;
|
||||
|
||||
protected NodeMgr nodeMgr;
|
||||
protected DataBuffer buffer;
|
||||
protected FixedKeyInteriorNode parent;
|
||||
protected int keyCount;
|
||||
|
||||
/**
|
||||
* Construct an existing fixed-length key node.
|
||||
* @param nodeMgr table node manager instance
|
||||
* @param buf node buffer
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
FixedKeyNode(NodeMgr nodeMgr, DataBuffer buf) throws IOException {
|
||||
this.nodeMgr = nodeMgr;
|
||||
buffer = buf;
|
||||
Schema schema = nodeMgr.getTableSchema();
|
||||
if (!schema.useFixedKeyNodes()) {
|
||||
throw new AssertException("unsupported schema");
|
||||
}
|
||||
keyType = schema.getKeyFieldType();
|
||||
keySize = keyType.length();
|
||||
keyCount = buffer.getInt(KEY_COUNT_OFFSET);
|
||||
nodeMgr.addNode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new fixed-length key node.
|
||||
* @param nodeMgr table node manager.
|
||||
* @param nodeType node type
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
FixedKeyNode(NodeMgr nodeMgr, byte nodeType) throws IOException {
|
||||
this.nodeMgr = nodeMgr;
|
||||
buffer = nodeMgr.getBufferMgr().createBuffer();
|
||||
NodeMgr.setNodeType(buffer, nodeType);
|
||||
Schema schema = nodeMgr.getTableSchema();
|
||||
if (!schema.useFixedKeyNodes()) {
|
||||
throw new AssertException("unsupported schema");
|
||||
}
|
||||
keyType = schema.getKeyFieldType();
|
||||
keySize = keyType.length();
|
||||
setKeyCount(0);
|
||||
nodeMgr.addNode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FixedKeyInteriorNode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferId() {
|
||||
return buffer.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root for this node. If setParent has not been invoked, this node
|
||||
* is assumed to be the root.
|
||||
* @return root node
|
||||
*/
|
||||
FixedKeyNode getRoot() {
|
||||
if (parent != null) {
|
||||
return parent.getRoot();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyCount() {
|
||||
return keyCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyCount(int cnt) {
|
||||
keyCount = cnt;
|
||||
buffer.putInt(KEY_COUNT_OFFSET, keyCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key value at a specific index.
|
||||
* @param index key index
|
||||
* @return key value
|
||||
*/
|
||||
abstract byte[] getKey(int index);
|
||||
|
||||
@Override
|
||||
public final Field getKeyField(int index) {
|
||||
Field key = keyType.newField();
|
||||
key.setBinaryData(getKey(index));
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,454 @@
|
||||
/* ###
|
||||
* 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 db;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
import ghidra.util.datastruct.IntArrayList;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* <code>FixedKeyVarRecNode</code> is an implementation of a BTree leaf node
|
||||
* which utilizes fixed-length key values and stores variable-length records.
|
||||
* <p>
|
||||
* This type of node has the following layout within a single DataBuffer
|
||||
* (field size in bytes, where 'L' is the fixed length of the fixed-length
|
||||
* key as specified by key type in associated Schema)::
|
||||
* <pre>
|
||||
* | NodeType(1) | KeyCount(4) | PrevLeafId(4) | NextLeafId(4) | Key0(L) | RecOffset0(4) | IndFlag0(1) |...
|
||||
*
|
||||
* | KeyN(L) | RecOffsetN(4) | IndFlagN(1) |...<FreeSpace>... | RecN |... | Rec1 |
|
||||
* </pre>
|
||||
* IndFlag - if not zero the record has been stored within a chained DBBuffer
|
||||
* whose 4-byte integer buffer ID has been stored within this leaf at the record offset.
|
||||
*/
|
||||
class FixedKeyVarRecNode extends FixedKeyRecordNode {
|
||||
|
||||
private static final int HEADER_SIZE = RECORD_LEAF_HEADER_SIZE;
|
||||
|
||||
private static final int OFFSET_SIZE = 4;
|
||||
private static final int INDIRECT_OPTION_SIZE = 1;
|
||||
|
||||
private static final int KEY_BASE_OFFSET = HEADER_SIZE;
|
||||
|
||||
private final int entrySize;
|
||||
private final int dataOffsetBaseOffset;
|
||||
private final int indirectOptionBaseOffset;
|
||||
|
||||
/**
|
||||
* Construct an existing fixed-length key variable-length record leaf node.
|
||||
* @param nodeMgr table node manager instance
|
||||
* @param buf node buffer
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
FixedKeyVarRecNode(NodeMgr nodeMgr, DataBuffer buf) throws IOException {
|
||||
super(nodeMgr, buf);
|
||||
entrySize = keySize + OFFSET_SIZE + INDIRECT_OPTION_SIZE;
|
||||
dataOffsetBaseOffset = KEY_BASE_OFFSET + keySize;
|
||||
indirectOptionBaseOffset = dataOffsetBaseOffset + OFFSET_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new fixed-length key variable-length record leaf node.
|
||||
* @param nodeMgr table node manager instance
|
||||
* @param prevLeafId node buffer id for previous leaf ( < 0: no leaf)
|
||||
* @param nextLeafId node buffer id for next leaf ( < 0 : no leaf)
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
FixedKeyVarRecNode(NodeMgr nodeMgr, int prevLeafId, int nextLeafId) throws IOException {
|
||||
super(nodeMgr, NodeMgr.FIXEDKEY_VAR_REC_NODE, prevLeafId, nextLeafId);
|
||||
entrySize = keySize + OFFSET_SIZE + INDIRECT_OPTION_SIZE;
|
||||
dataOffsetBaseOffset = KEY_BASE_OFFSET + keySize;
|
||||
indirectOptionBaseOffset = dataOffsetBaseOffset + OFFSET_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
FixedKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
|
||||
return new FixedKeyVarRecNode(nodeMgr, prevLeafId, nextLeafId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyOffset(int index) {
|
||||
return KEY_BASE_OFFSET + (index * entrySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the record offset within the buffer
|
||||
* @param index key index
|
||||
* @return record offset
|
||||
*/
|
||||
public int getRecordDataOffset(int index) {
|
||||
return buffer.getInt(dataOffsetBaseOffset + (index * entrySize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the record offset within the buffer for the specified key index
|
||||
* @param index key index
|
||||
* @param offset record offset
|
||||
*/
|
||||
private void putRecordDataOffset(int index, int offset) {
|
||||
buffer.putInt(dataOffsetBaseOffset + (index * entrySize), offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a record is utilizing a chained DBBuffer for data storage
|
||||
* @param index key index
|
||||
* @return true if indirect storage is used for record, else false
|
||||
*/
|
||||
private boolean hasIndirectStorage(int index) {
|
||||
return buffer.getByte(indirectOptionBaseOffset + (index * entrySize)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the indirect storage flag associated with a record
|
||||
* @param index key index
|
||||
* @param state indirect storage used (true) or not used (false)
|
||||
*/
|
||||
private void enableIndirectStorage(int index, boolean state) {
|
||||
buffer.putByte(indirectOptionBaseOffset + (index * entrySize), state ? (byte) 1 : (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return unused free space within node
|
||||
*/
|
||||
private int getFreeSpace() {
|
||||
return (keyCount == 0 ? buffer.length() : getRecordDataOffset(keyCount - 1)) -
|
||||
(keyCount * entrySize) - RECORD_LEAF_HEADER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of a stored record.
|
||||
* @param index index associated with record.
|
||||
*/
|
||||
private int getRecordLength(int index) {
|
||||
if (index == 0) {
|
||||
return buffer.length() - getRecordDataOffset(0);
|
||||
}
|
||||
return getRecordDataOffset(index - 1) - getRecordDataOffset(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of a stored record. Optimized if record offset
|
||||
* already known.
|
||||
* @param index index associated with record.
|
||||
* @param offset record offset
|
||||
*/
|
||||
private int getRecordLength(int index, int offset) {
|
||||
if (index == 0) {
|
||||
return buffer.length() - offset;
|
||||
}
|
||||
return getRecordDataOffset(index - 1) - offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all record data, starting with index, by the specified offset amount.
|
||||
* If the node contains 5 records, an index of 3 would shift the record data
|
||||
* for indexes 3 and 4 left by the spacified offset amount. This is used to
|
||||
* make space for a new or updated record.
|
||||
* @param index the smaller key/record index (0 <= index1)
|
||||
* @param offset movement offset in bytes
|
||||
* @return insertion offset immediately following moved block.
|
||||
*/
|
||||
private int moveRecords(int index, int offset) {
|
||||
|
||||
int lastIndex = keyCount - 1;
|
||||
|
||||
// No movement needed for appended record
|
||||
if (index == keyCount) {
|
||||
if (index == 0) {
|
||||
return buffer.length() + offset;
|
||||
}
|
||||
return getRecordDataOffset(lastIndex) + offset;
|
||||
}
|
||||
|
||||
// Determine block to be moved
|
||||
int start = getRecordDataOffset(lastIndex);
|
||||
int end = (index == 0) ? buffer.length() : getRecordDataOffset(index - 1);
|
||||
int len = end - start;
|
||||
|
||||
// Move record data
|
||||
buffer.move(start, start + offset, len);
|
||||
|
||||
// Adjust stored offsets
|
||||
for (int i = index; i < keyCount; i++) {
|
||||
putRecordDataOffset(i, getRecordDataOffset(i) + offset);
|
||||
}
|
||||
return end + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record getRecord(Schema schema, int index) throws IOException {
|
||||
Field key = getKeyField(index);
|
||||
Record record = schema.createRecord(key);
|
||||
if (hasIndirectStorage(index)) {
|
||||
int bufId = buffer.getInt(getRecordDataOffset(index));
|
||||
ChainedBuffer chainedBuffer = new ChainedBuffer(nodeMgr.getBufferMgr(), bufId);
|
||||
record.read(chainedBuffer, 0);
|
||||
}
|
||||
else {
|
||||
record.read(buffer, getRecordDataOffset(index));
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRecordOffset(int index) throws IOException {
|
||||
if (hasIndirectStorage(index)) {
|
||||
return -buffer.getInt(getRecordDataOffset(index));
|
||||
}
|
||||
return getRecordDataOffset(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record getRecord(Field key, Schema schema) throws IOException {
|
||||
int index = getKeyIndex(key);
|
||||
if (index < 0)
|
||||
return null;
|
||||
return getRecord(schema, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index which represents the halfway point within the record data.
|
||||
* @returns key index.
|
||||
*/
|
||||
private int getSplitIndex() {
|
||||
|
||||
int halfway = ((keyCount == 0 ? buffer.length() : getRecordDataOffset(keyCount - 1)) +
|
||||
buffer.length()) / 2;
|
||||
int min = 1;
|
||||
int max = keyCount - 1;
|
||||
|
||||
while (min < max) {
|
||||
int i = (min + max) / 2;
|
||||
int offset = getRecordDataOffset(i);
|
||||
if (offset == halfway) {
|
||||
return i;
|
||||
}
|
||||
else if (offset < halfway) {
|
||||
max = i - 1;
|
||||
}
|
||||
else {
|
||||
min = i + 1;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
@Override
|
||||
void splitData(FixedKeyRecordNode newRightLeaf) {
|
||||
|
||||
FixedKeyVarRecNode rightNode = (FixedKeyVarRecNode) newRightLeaf;
|
||||
|
||||
int splitIndex = getSplitIndex();
|
||||
int count = keyCount - splitIndex;
|
||||
int start = getRecordDataOffset(keyCount - 1); // start of block to be moved
|
||||
int end = getRecordDataOffset(splitIndex - 1); // end of block to be moved
|
||||
int splitLen = end - start; // length of block to be moved
|
||||
int rightOffset = buffer.length() - splitLen; // data offset within new leaf node
|
||||
|
||||
// Copy data to new leaf node
|
||||
DataBuffer newBuf = rightNode.buffer;
|
||||
newBuf.copy(rightOffset, buffer, start, splitLen);
|
||||
newBuf.copy(KEY_BASE_OFFSET, buffer, KEY_BASE_OFFSET + (splitIndex * entrySize),
|
||||
count * entrySize);
|
||||
|
||||
// Fix record offsets in new leaf node
|
||||
int offsetCorrection = buffer.length() - end;
|
||||
for (int i = 0; i < count; i++) {
|
||||
rightNode.putRecordDataOffset(i, rightNode.getRecordDataOffset(i) + offsetCorrection);
|
||||
}
|
||||
|
||||
// Adjust key counts
|
||||
setKeyCount(keyCount - count);
|
||||
rightNode.setKeyCount(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
FixedKeyNode updateRecord(int index, Record record) throws IOException {
|
||||
|
||||
int offset = getRecordDataOffset(index);
|
||||
int oldLen = getRecordLength(index, offset);
|
||||
int len = record.length();
|
||||
|
||||
// Check for use of indirect chained record node(s)
|
||||
int maxRecordLength = ((buffer.length() - HEADER_SIZE) >> 2) - entrySize; // min 4 records per node
|
||||
boolean wasIndirect = hasIndirectStorage(index);
|
||||
boolean useIndirect = (len > maxRecordLength);
|
||||
|
||||
if (useIndirect) {
|
||||
// Store record in chained buffers
|
||||
len = 4;
|
||||
ChainedBuffer chainedBuffer = null;
|
||||
if (wasIndirect) {
|
||||
chainedBuffer = new ChainedBuffer(nodeMgr.getBufferMgr(), buffer.getInt(offset));
|
||||
chainedBuffer.setSize(record.length(), false);
|
||||
}
|
||||
else {
|
||||
chainedBuffer = new ChainedBuffer(record.length(), nodeMgr.getBufferMgr());
|
||||
buffer.putInt(offset + oldLen - 4, chainedBuffer.getId()); // assumes old len is always > 4
|
||||
enableIndirectStorage(index, true);
|
||||
}
|
||||
record.write(chainedBuffer, 0);
|
||||
}
|
||||
else if (wasIndirect) {
|
||||
removeChainedBuffer(buffer.getInt(offset));
|
||||
enableIndirectStorage(index, false);
|
||||
}
|
||||
|
||||
// See if updated record will fit in current buffer
|
||||
if (useIndirect || len <= (getFreeSpace() + oldLen)) {
|
||||
|
||||
// Overwrite record data - move other data if needed
|
||||
int dataShift = oldLen - len;
|
||||
if (dataShift != 0) {
|
||||
offset = moveRecords(index + 1, dataShift);
|
||||
putRecordDataOffset(index, offset);
|
||||
}
|
||||
if (!useIndirect) {
|
||||
record.write(buffer, offset);
|
||||
}
|
||||
return getRoot();
|
||||
}
|
||||
|
||||
// Insufficient room for updated record - remove and re-add
|
||||
Field key = record.getKeyField();
|
||||
FixedKeyRecordNode leaf = (FixedKeyRecordNode) deleteRecord(key, null).getLeafNode(key);
|
||||
return leaf.putRecord(record, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified record at the specified key index.
|
||||
* Existing data may be shifted within the buffer to make room for
|
||||
* the new record. Parent must be notified if this changes the leftmost
|
||||
* key.
|
||||
* @param index insertion index for stored key
|
||||
* @param record record to be inserted
|
||||
* @throws IOException thrown if an IO error occurs
|
||||
*/
|
||||
@Override
|
||||
boolean insertRecord(int index, Record record) throws IOException {
|
||||
|
||||
// Check for use of indirect chained record node(s)
|
||||
int len = record.length();
|
||||
int maxRecordLength = ((buffer.length() - HEADER_SIZE) >> 2) - entrySize; // min 4 records per node
|
||||
boolean useIndirect = (len > maxRecordLength);
|
||||
if (useIndirect) {
|
||||
len = 4;
|
||||
}
|
||||
|
||||
if ((len + entrySize) > getFreeSpace())
|
||||
return false; // insufficient space for record storage
|
||||
|
||||
// Make room for new record
|
||||
int offset = moveRecords(index, -len);
|
||||
|
||||
// Make room for new key/offset entry
|
||||
int start = KEY_BASE_OFFSET + (index * entrySize);
|
||||
len = (keyCount - index) * entrySize;
|
||||
buffer.move(start, start + entrySize, len);
|
||||
|
||||
// Store new record key/offset
|
||||
buffer.put(start, record.getKeyField().getBinaryData());
|
||||
buffer.putInt(start + keySize, offset);
|
||||
setKeyCount(keyCount + 1);
|
||||
|
||||
// Store record data
|
||||
if (useIndirect) {
|
||||
ChainedBuffer chainedBuffer =
|
||||
new ChainedBuffer(record.length(), nodeMgr.getBufferMgr());
|
||||
buffer.putInt(offset, chainedBuffer.getId());
|
||||
record.write(chainedBuffer, 0);
|
||||
}
|
||||
else {
|
||||
record.write(buffer, offset);
|
||||
}
|
||||
enableIndirectStorage(index, useIndirect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(int index) throws IOException {
|
||||
|
||||
if (index < 0 || index >= keyCount)
|
||||
throw new AssertException();
|
||||
|
||||
if (hasIndirectStorage(index)) {
|
||||
removeChainedBuffer(buffer.getInt(getRecordDataOffset(index)));
|
||||
enableIndirectStorage(index, false);
|
||||
}
|
||||
|
||||
int len = getRecordLength(index);
|
||||
moveRecords(index + 1, len);
|
||||
|
||||
int start = KEY_BASE_OFFSET + ((index + 1) * entrySize);
|
||||
len = (keyCount - index - 1) * entrySize;
|
||||
buffer.move(start, start - entrySize, len);
|
||||
setKeyCount(keyCount - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FixedKeyNode removeLeaf() throws IOException {
|
||||
|
||||
// Remove all chained buffers associated with this leaf
|
||||
for (int index = 0; index < keyCount; ++index) {
|
||||
if (hasIndirectStorage(index)) {
|
||||
removeChainedBuffer(buffer.getInt(getRecordDataOffset(index)));
|
||||
}
|
||||
}
|
||||
return super.removeLeaf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a chained buffer.
|
||||
* @param bufferId chained buffer ID
|
||||
*/
|
||||
private void removeChainedBuffer(int bufferId) throws IOException {
|
||||
ChainedBuffer chainedBuffer = new ChainedBuffer(nodeMgr.getBufferMgr(), bufferId);
|
||||
chainedBuffer.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
|
||||
// Remove all chained buffers associated with this node.
|
||||
for (int index = 0; index < keyCount; index++) {
|
||||
if (hasIndirectStorage(index)) {
|
||||
int offset = getRecordDataOffset(index);
|
||||
int bufferId = buffer.getInt(offset);
|
||||
removeChainedBuffer(bufferId);
|
||||
buffer.putInt(offset, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this node
|
||||
nodeMgr.deleteNode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getBufferReferences() {
|
||||
IntArrayList idList = new IntArrayList();
|
||||
for (int i = 0; i < keyCount; i++) {
|
||||
if (hasIndirectStorage(i)) {
|
||||
int offset = getRecordDataOffset(i);
|
||||
idList.add(buffer.getInt(offset));
|
||||
}
|
||||
}
|
||||
return idList.toArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +15,10 @@
|
||||
*/
|
||||
package db;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* <code>FixedRecNode</code> is an implementation of a BTree leaf node
|
||||
@@ -37,75 +35,68 @@ import db.buffers.DataBuffer;
|
||||
class FixedRecNode extends LongKeyRecordNode {
|
||||
|
||||
private static final int HEADER_SIZE = RECORD_LEAF_HEADER_SIZE;
|
||||
|
||||
|
||||
private static final int ENTRY_BASE_OFFSET = HEADER_SIZE;
|
||||
|
||||
|
||||
private static final int KEY_SIZE = 8;
|
||||
|
||||
|
||||
private static final int[] EMPTY_ID_LIST = new int[0];
|
||||
|
||||
private int entrySize;
|
||||
private int recordLength;
|
||||
|
||||
|
||||
/**
|
||||
* Construct an existing long-key fixed-length record leaf node.
|
||||
* @param nodeMgr table node manager instance
|
||||
* @param buf node buffer
|
||||
* @param recordLength fixed record length
|
||||
*/
|
||||
FixedRecNode(NodeMgr nodeMgr, DataBuffer buf, int recordLength) {
|
||||
super(nodeMgr, buf);
|
||||
this.recordLength = recordLength;
|
||||
entrySize = KEY_SIZE + recordLength;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new long-key fixed-length record leaf node.
|
||||
* @param nodeMgr table node manager instance
|
||||
* @param recordLength fixed record length
|
||||
* @param prevLeafId node buffer id for previous leaf ( < 0: no leaf)
|
||||
* @param nextLeafId node buffer id for next leaf ( < 0 : no leaf)
|
||||
* @throws IOException
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
FixedRecNode(NodeMgr nodeMgr, int recordLength, int prevLeafId, int nextLeafId) throws IOException {
|
||||
FixedRecNode(NodeMgr nodeMgr, int recordLength, int prevLeafId, int nextLeafId)
|
||||
throws IOException {
|
||||
super(nodeMgr, NodeMgr.LONGKEY_FIXED_REC_NODE, prevLeafId, nextLeafId);
|
||||
this.recordLength = recordLength;
|
||||
entrySize = KEY_SIZE + recordLength;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyRecordNode#createNewLeaf(int, int)
|
||||
*/
|
||||
@Override
|
||||
LongKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
|
||||
LongKeyRecordNode createNewLeaf(int prevLeafId, int nextLeafId) throws IOException {
|
||||
return new FixedRecNode(nodeMgr, recordLength, prevLeafId, nextLeafId);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyNode#getKey(int)
|
||||
*/
|
||||
|
||||
@Override
|
||||
long getKey(int index) {
|
||||
return buffer.getLong(ENTRY_BASE_OFFSET + (index * entrySize));
|
||||
long getKey(int index) {
|
||||
return buffer.getLong(getKeyOffset(index));
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Store a key at the specified index
|
||||
// * @param index key index
|
||||
// * @param key key value
|
||||
// */
|
||||
// private void putKey(int index, long key) {
|
||||
// buffer.putLong(ENTRY_BASE_OFFSET + (index * entrySize), key);
|
||||
// }
|
||||
|
||||
|
||||
@Override
|
||||
public int getKeyOffset(int index) {
|
||||
return ENTRY_BASE_OFFSET + (index * entrySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the record offset within the buffer
|
||||
* @param index key index
|
||||
* @return record offset
|
||||
*/
|
||||
private int getRecordOffset(int index) {
|
||||
@Override
|
||||
public int getRecordOffset(int index) {
|
||||
return ENTRY_BASE_OFFSET + (index * entrySize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shift all records by one starting with index to the end.
|
||||
* @param index the smaller key index (0 <= index1)
|
||||
@@ -113,49 +104,41 @@ class FixedRecNode extends LongKeyRecordNode {
|
||||
* one record.
|
||||
*/
|
||||
private void shiftRecords(int index, boolean rightShift) {
|
||||
|
||||
|
||||
// No movement needed for appended record
|
||||
if (index == keyCount)
|
||||
return;
|
||||
|
||||
|
||||
// Determine block to be moved
|
||||
int start = getRecordOffset(index);
|
||||
int end = getRecordOffset(keyCount);
|
||||
int len = end - start;
|
||||
|
||||
|
||||
// Move record data
|
||||
int offset = start + (rightShift ? entrySize : -entrySize);
|
||||
buffer.move(start, offset, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyRecordNode#remove(int)
|
||||
*/
|
||||
@Override
|
||||
void remove(int index) {
|
||||
|
||||
if (index < 0 || index >= keyCount)
|
||||
throw new AssertException();
|
||||
@Override
|
||||
public void remove(int index) {
|
||||
|
||||
if (index < 0 || index >= keyCount)
|
||||
throw new AssertException();
|
||||
|
||||
shiftRecords(index + 1, false);
|
||||
setKeyCount(keyCount - 1);
|
||||
setKeyCount(keyCount - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyRecordNode#insertRecord(int, ghidra.framework.store.db.Record)
|
||||
*/
|
||||
@Override
|
||||
boolean insertRecord(int index, Record record) throws IOException {
|
||||
|
||||
// Check for use of indirect chained record node(s)
|
||||
// int len = record.length();
|
||||
boolean insertRecord(int index, Record record) throws IOException {
|
||||
|
||||
if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize))
|
||||
if (keyCount == ((buffer.length() - HEADER_SIZE) / entrySize)) {
|
||||
return false; // insufficient space for record storage
|
||||
}
|
||||
|
||||
// Make room for new record
|
||||
shiftRecords(index, true);
|
||||
|
||||
|
||||
// Store new record
|
||||
int offset = getRecordOffset(index);
|
||||
buffer.putLong(offset, record.getKey());
|
||||
@@ -165,21 +148,15 @@ throw new AssertException();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyRecordNode#updateRecord(int, ghidra.framework.store.db.Record)
|
||||
*/
|
||||
@Override
|
||||
LongKeyNode updateRecord(int index, Record record) throws IOException {
|
||||
LongKeyNode updateRecord(int index, Record record) throws IOException {
|
||||
int offset = getRecordOffset(index) + KEY_SIZE;
|
||||
record.write(buffer, offset);
|
||||
return getRoot();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyRecordNode#getRecord(long, ghidra.framework.store.db.Schema)
|
||||
*/
|
||||
@Override
|
||||
Record getRecord(long key, Schema schema) throws IOException {
|
||||
Record getRecord(long key, Schema schema) throws IOException {
|
||||
int index = getKeyIndex(key);
|
||||
if (index < 0)
|
||||
return null;
|
||||
@@ -188,50 +165,39 @@ throw new AssertException();
|
||||
return record;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyRecordNode#getRecord(ghidra.framework.store.db.Schema, int)
|
||||
*/
|
||||
@Override
|
||||
Record getRecord(Schema schema, int index) throws IOException {
|
||||
public Record getRecord(Schema schema, int index) throws IOException {
|
||||
long key = getKey(index);
|
||||
Record record = schema.createRecord(key);
|
||||
record.read(buffer, getRecordOffset(index) + KEY_SIZE);
|
||||
return record;
|
||||
return record;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyRecordNode#splitData(ghidra.framework.store.db.LongKeyRecordNode)
|
||||
*/
|
||||
@Override
|
||||
void splitData(LongKeyRecordNode newRightLeaf) {
|
||||
|
||||
void splitData(LongKeyRecordNode newRightLeaf) {
|
||||
|
||||
FixedRecNode rightNode = (FixedRecNode) newRightLeaf;
|
||||
|
||||
|
||||
int splitIndex = keyCount / 2;
|
||||
int count = keyCount - splitIndex;
|
||||
int start = getRecordOffset(splitIndex); // start of block to be moved
|
||||
int end = getRecordOffset(keyCount); // end of block to be moved
|
||||
int splitLen = end - start; // length of block to be moved
|
||||
|
||||
|
||||
// Copy data to new leaf node
|
||||
rightNode.buffer.copy(ENTRY_BASE_OFFSET, buffer, start, splitLen);
|
||||
|
||||
rightNode.buffer.copy(ENTRY_BASE_OFFSET, buffer, start, splitLen);
|
||||
|
||||
// Adjust key counts
|
||||
setKeyCount(keyCount - count);
|
||||
rightNode.setKeyCount(count);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.LongKeyNode#delete()
|
||||
*/
|
||||
@Override
|
||||
public void delete() throws IOException {
|
||||
public void delete() throws IOException {
|
||||
nodeMgr.deleteNode(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.db.BTreeNode#getBufferReferences()
|
||||
*/
|
||||
@Override
|
||||
public int[] getBufferReferences() {
|
||||
return EMPTY_ID_LIST;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,4 +26,13 @@ public class IllegalFieldAccessException extends RuntimeException {
|
||||
IllegalFieldAccessException() {
|
||||
super("Illegal field access");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an illegal field access exception
|
||||
* with a specific message
|
||||
*/
|
||||
IllegalFieldAccessException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package db;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
|
||||
/**
|
||||
* <code>IndexBuffer</code> stores index data for a common index key
|
||||
* within a data buffer. The index data has the following layout (field size in
|
||||
* bytes):
|
||||
* <pre>
|
||||
* | FieldType(1) | KeyCount(4) | PrimeKey1(8) | ... | PrimeKeyN(8) |
|
||||
* </pre>
|
||||
* This type of index buffer is used to store primary keys associated with a
|
||||
* single secondary key. The association to a specific secondary key
|
||||
* is handled by the <code>IndexTable</code>. The primary keys are maintained
|
||||
* within the buffer in an asscending sorted order.
|
||||
*/
|
||||
class IndexBuffer {
|
||||
|
||||
private static final int FIELD_TYPE_SIZE = 1;
|
||||
private static final int KEY_COUNT_SIZE = 4;
|
||||
|
||||
private static final int FIELD_TYPE_OFFSET = 0;
|
||||
private static final int KEY_COUNT_OFFSET = FIELD_TYPE_OFFSET + FIELD_TYPE_SIZE;
|
||||
|
||||
static final int INDEX_HEADER_SIZE = FIELD_TYPE_SIZE + KEY_COUNT_SIZE;
|
||||
|
||||
static final int PRIMARY_KEY_SIZE = 8;
|
||||
|
||||
Field indexKey;
|
||||
int keyCount;
|
||||
IndexDataBuffer dataBuffer;
|
||||
|
||||
/**
|
||||
* Construct a new index buffer.
|
||||
* @param indexKey associated index key
|
||||
* @param data existing index buffer data from storage or null for an
|
||||
* empty index buffer.
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
IndexBuffer(Field indexKey, byte[] data) throws IOException {
|
||||
this.indexKey = indexKey;
|
||||
if (data == null) {
|
||||
dataBuffer = new IndexDataBuffer(INDEX_HEADER_SIZE);
|
||||
dataBuffer.putByte(FIELD_TYPE_OFFSET, indexKey.getFieldType());
|
||||
dataBuffer.putInt(KEY_COUNT_OFFSET, 0);
|
||||
}
|
||||
else {
|
||||
if (data[FIELD_TYPE_OFFSET] != indexKey.getFieldType())
|
||||
throw new IOException("Invalid index data");
|
||||
dataBuffer = new IndexDataBuffer(data);
|
||||
}
|
||||
keyCount = dataBuffer.getInt(KEY_COUNT_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated index key
|
||||
* @return index key
|
||||
*/
|
||||
Field getIndexKey() {
|
||||
return indexKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the stored primary key count
|
||||
* @param cnt primary key count
|
||||
*/
|
||||
private void setKeyCount(int cnt) {
|
||||
keyCount = cnt;
|
||||
dataBuffer.putInt(KEY_COUNT_OFFSET, keyCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data buffer manipulation for the index data
|
||||
*/
|
||||
class IndexDataBuffer extends DataBuffer {
|
||||
|
||||
/**
|
||||
* Construct an index data buffer.
|
||||
* @see db.buffers.DataBuffer#DataBuffer(byte[])
|
||||
*/
|
||||
IndexDataBuffer(byte[] data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an index data buffer.
|
||||
* @see db.buffers.DataBuffer#DataBuffer(int)
|
||||
*/
|
||||
IndexDataBuffer(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage array associated with this buffer.
|
||||
* @return byte storage array.
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage array associated with this buffer.
|
||||
* @return byte storage array.
|
||||
*/
|
||||
@Override
|
||||
protected void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index buffer data.
|
||||
* @return index data or null if index data is empty.
|
||||
*/
|
||||
byte[] getData() {
|
||||
byte[] data = dataBuffer.getData();
|
||||
if (data.length <= INDEX_HEADER_SIZE)
|
||||
return null;
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary key associated with the specified entry index.
|
||||
* This method does not perform any bounds checking on the index value.
|
||||
* @param index index entry index.
|
||||
* @return primary key associated with entry.
|
||||
*/
|
||||
long getPrimaryKey(int index) {
|
||||
return dataBuffer.getLong(INDEX_HEADER_SIZE + (index * PRIMARY_KEY_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the secondary key index within the buffer.
|
||||
* @param primaryKey primary key
|
||||
* @return key index if found, else -(key index + 1) indicates insertion
|
||||
* point.
|
||||
*/
|
||||
int getIndex(long primaryKey) {
|
||||
return getKeyIndex(primaryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a binary search to locate the specified primary key.
|
||||
* @param primaryKey primary key
|
||||
* @return key index if found, else -(key index + 1) indicates insertion
|
||||
* point.
|
||||
*/
|
||||
private int getKeyIndex(long primaryKey) {
|
||||
|
||||
int min = 0;
|
||||
int max = keyCount - 1;
|
||||
|
||||
while (min <= max) {
|
||||
int i = (min + max)/2;
|
||||
long k = getPrimaryKey(i);
|
||||
if (k == primaryKey) {
|
||||
return i;
|
||||
}
|
||||
else if (k < primaryKey) {
|
||||
min = i + 1;
|
||||
}
|
||||
else {
|
||||
max = i - 1;
|
||||
}
|
||||
}
|
||||
return -(min+1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new primary key to this index buffer.
|
||||
* @param primaryKey primary key
|
||||
*/
|
||||
void addEntry(long primaryKey) {
|
||||
int index = getKeyIndex(primaryKey);
|
||||
if (index < 0) {
|
||||
index = -index-1;
|
||||
IndexDataBuffer newDataBuffer = new IndexDataBuffer(dataBuffer.length() + PRIMARY_KEY_SIZE);
|
||||
int len = INDEX_HEADER_SIZE + (index * PRIMARY_KEY_SIZE);
|
||||
newDataBuffer.copy(0, dataBuffer, 0, len);
|
||||
newDataBuffer.copy(len + PRIMARY_KEY_SIZE, dataBuffer, len, dataBuffer.length() - len);
|
||||
newDataBuffer.putLong(len, primaryKey);
|
||||
dataBuffer = newDataBuffer;
|
||||
setKeyCount(keyCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified index entry from this index buffer.
|
||||
* @param primaryKey primary key
|
||||
*/
|
||||
void deleteEntry(long primaryKey) {
|
||||
int index = getKeyIndex(primaryKey);
|
||||
if (index >= 0) {
|
||||
IndexDataBuffer newDataBuffer = new IndexDataBuffer(dataBuffer.length() - PRIMARY_KEY_SIZE);
|
||||
int len = INDEX_HEADER_SIZE + (index * PRIMARY_KEY_SIZE);
|
||||
newDataBuffer.copy(0, dataBuffer, 0, len);
|
||||
newDataBuffer.copy(len, dataBuffer, len + PRIMARY_KEY_SIZE, dataBuffer.length() - len - PRIMARY_KEY_SIZE);
|
||||
dataBuffer = newDataBuffer;
|
||||
setKeyCount(keyCount - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of primary keys contained within this index buffer.
|
||||
* @return long[] list of primary keys
|
||||
* @throws IOException thrown if IO error occurs
|
||||
*/
|
||||
long[] getPrimaryKeys() {
|
||||
long[] keys = new long[keyCount];
|
||||
for (int i = 0; i < keyCount; i++) {
|
||||
keys[i] = getPrimaryKey(i);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,15 +15,30 @@
|
||||
*/
|
||||
package db;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.buffers.DataBuffer;
|
||||
|
||||
/**
|
||||
* <code>IntField</code> provides a wrapper for 4-byte signed integer data
|
||||
* which is read or written to a Record.
|
||||
*/
|
||||
public class IntField extends Field {
|
||||
public final class IntField extends Field {
|
||||
|
||||
/**
|
||||
* Minimum integer field value
|
||||
*/
|
||||
public static final IntField MIN_VALUE = new IntField(Integer.MIN_VALUE, true);
|
||||
|
||||
/**
|
||||
* Maximum integer field value
|
||||
*/
|
||||
public static final IntField MAX_VALUE = new IntField(Integer.MAX_VALUE, true);
|
||||
|
||||
/**
|
||||
* Instance intended for defining a {@link Table} {@link Schema}
|
||||
*/
|
||||
public static final IntField INSTANCE = MIN_VALUE;
|
||||
|
||||
private int value;
|
||||
|
||||
@@ -39,69 +53,57 @@ public class IntField extends Field {
|
||||
* @param i initial value
|
||||
*/
|
||||
public IntField(int i) {
|
||||
value = i;
|
||||
this(i, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#getIntValue()
|
||||
* Construct an integer field with an initial value of i.
|
||||
* @param i initial value
|
||||
* @param immutable true if field value is immutable
|
||||
*/
|
||||
IntField(int i, boolean immutable) {
|
||||
super(immutable);
|
||||
value = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#setIntValue(int)
|
||||
*/
|
||||
@Override
|
||||
public void setIntValue(int value) {
|
||||
checkImmutable();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#length()
|
||||
*/
|
||||
@Override
|
||||
int length() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#write(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int write(Buffer buf, int offset) throws IOException {
|
||||
return buf.putInt(offset, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#read(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int read(Buffer buf, int offset) throws IOException {
|
||||
checkImmutable();
|
||||
value = buf.getInt(offset);
|
||||
return offset + 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#readLength(ghidra.framework.store.Buffer, int)
|
||||
*/
|
||||
@Override
|
||||
int readLength(Buffer buf, int offset) throws IOException {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#getFieldType()
|
||||
*/
|
||||
@Override
|
||||
protected byte getFieldType() {
|
||||
byte getFieldType() {
|
||||
return INT_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IntField: " + Integer.toString(value);
|
||||
@@ -109,12 +111,9 @@ public class IntField extends Field {
|
||||
|
||||
@Override
|
||||
public String getValueAsString() {
|
||||
return Integer.toHexString(value);
|
||||
return "0x" + Integer.toHexString(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof IntField))
|
||||
@@ -122,9 +121,6 @@ public class IntField extends Field {
|
||||
return ((IntField) obj).value == value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Field o) {
|
||||
IntField f = (IntField) o;
|
||||
@@ -135,54 +131,64 @@ public class IntField extends Field {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#newField(docking.widgets.fieldpanel.Field)
|
||||
*/
|
||||
@Override
|
||||
public Field newField(Field fieldValue) {
|
||||
if (fieldValue.isVariableLength())
|
||||
throw new AssertException();
|
||||
return new IntField((int) fieldValue.getLongValue());
|
||||
int compareTo(DataBuffer buffer, int offset) {
|
||||
int otherValue = buffer.getInt(offset);
|
||||
if (value == otherValue)
|
||||
return 0;
|
||||
else if (value < otherValue)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#newField()
|
||||
*/
|
||||
@Override
|
||||
public Field newField() {
|
||||
public IntField copyField() {
|
||||
return new IntField((int) getLongValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntField newField() {
|
||||
return new IntField();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#getLongValue()
|
||||
*/
|
||||
@Override
|
||||
public long getLongValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#setLongValue(long)
|
||||
*/
|
||||
@Override
|
||||
public void setLongValue(long value) {
|
||||
this.value = (int) value;
|
||||
setIntValue((int) value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see db.Field#getBinaryData()
|
||||
*/
|
||||
@Override
|
||||
public byte[] getBinaryData() {
|
||||
return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8),
|
||||
(byte) value };
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public void setBinaryData(byte[] bytes) {
|
||||
checkImmutable();
|
||||
if (bytes.length != 4) {
|
||||
throw new IllegalFieldAccessException();
|
||||
}
|
||||
value = ((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) |
|
||||
(bytes[3] & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
IntField getMinValue() {
|
||||
return MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
IntField getMaxValue() {
|
||||
return MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,8 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.feature.vt.api.db;
|
||||
|
||||
public class VTAppliedMarkupTableDBAdapterV0 {
|
||||
package db;
|
||||
|
||||
/**
|
||||
* Marker interface for {@link Table} interior nodes within the BTree structure.
|
||||
*/
|
||||
public interface InteriorNode extends BTreeNode {
|
||||
// marker interface only
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user