Updated the DBViewer to allow for filtering

This commit is contained in:
dragonmacher 2023-11-30 16:08:15 -05:00
parent 5fd01c739d
commit ff7c8929bc
18 changed files with 343 additions and 565 deletions

View file

@ -17,20 +17,20 @@ package ghidra.app.plugin.debug;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.FlowLayout; import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.TableModel;
import db.*; import db.*;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel; import docking.widgets.label.GLabel;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import ghidra.app.plugin.debug.dbtable.*; import docking.widgets.table.GTableFilterPanel;
import docking.widgets.table.threaded.GThreadedTablePanel;
import ghidra.app.plugin.debug.dbtable.DbSmallTableModel;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.SwingUpdateManager;
@ -39,25 +39,27 @@ class DbViewerComponent extends JPanel {
private static Table[] NO_TABLES = new Table[0]; private static Table[] NO_TABLES = new Table[0];
private static Comparator<Table> TABLE_NAME_COMPARATOR = new Comparator<>() { private static Comparator<Table> TABLE_NAME_COMPARATOR =
@Override (o1, o2) -> (o1).getName().compareTo((o2).getName());
public int compare(Table o1, Table o2) {
return (o1).getName().compareTo((o2).getName());
}
};
private DBHandle dbh; private DBHandle dbh;
private DBListener dbListener; private DBListener dbListener;
private JPanel southPanel; private JPanel centerPanel;
private JComponent southComponent;
private JLabel dbLabel; private JLabel dbLabel;
private JComboBox<TableItem> combo; private JComboBox<TableItem> combo;
private Table[] tables = NO_TABLES; private Table[] tables = NO_TABLES;
private Hashtable<String, TableStatistics[]> tableStats = new Hashtable<>(); private Map<String, TableStatistics[]> tableStats = new HashMap<>();
private SwingUpdateManager updateMgr; private SwingUpdateManager updateMgr;
DbViewerComponent() { private PluginTool tool;
private GTableFilterPanel<DBRecord> tableFilterPanel;
DbViewerComponent(PluginTool tool) {
super(new BorderLayout()); super(new BorderLayout());
this.tool = tool;
JPanel northPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel northPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JPanel subNorthPanel = new JPanel(new PairLayout(4, 10)); JPanel subNorthPanel = new JPanel(new PairLayout(4, 10));
@ -66,32 +68,19 @@ class DbViewerComponent extends JPanel {
subNorthPanel.add(dbLabel); subNorthPanel.add(dbLabel);
subNorthPanel.add(new GLabel("Tables:")); subNorthPanel.add(new GLabel("Tables:"));
combo = new GComboBox<>(); combo = new GComboBox<>();
combo.addActionListener(new ActionListener() { combo.addActionListener(e -> refreshTable());
@Override
public void actionPerformed(ActionEvent e) {
refreshTable();
}
});
subNorthPanel.add(combo); subNorthPanel.add(combo);
northPanel.add(subNorthPanel); northPanel.add(subNorthPanel);
add(northPanel, BorderLayout.NORTH); add(northPanel, BorderLayout.NORTH);
updateMgr = new SwingUpdateManager(100, 2000, new Runnable() { updateMgr = new SwingUpdateManager(100, 2000, () -> refresh());
@Override
public void run() {
refresh();
}
});
} }
synchronized void closeDatabase() { synchronized void closeDatabase() {
if (dbh != null) { if (dbh != null) {
combo.removeAllItems(); combo.removeAllItems();
dbLabel.setText(""); dbLabel.setText("");
if (southPanel != null) { removeWidgets();
remove(southPanel);
southPanel = null;
}
tables = NO_TABLES; tables = NO_TABLES;
tableStats.clear(); tableStats.clear();
dbh = null; dbh = null;
@ -101,6 +90,15 @@ class DbViewerComponent extends JPanel {
} }
} }
private void removeWidgets() {
if (centerPanel != null) {
remove(centerPanel);
remove(southComponent);
centerPanel = null;
southComponent = null;
}
}
synchronized void openDatabase(String name, DBHandle handle) { synchronized void openDatabase(String name, DBHandle handle) {
closeDatabase(); closeDatabase();
@ -110,7 +108,7 @@ class DbViewerComponent extends JPanel {
dbLabel.setText(name); dbLabel.setText(name);
updateTableChoices(null); updateTableChoices(null);
dbListener = getNewDBListener(); dbListener = new InternalDBListener();
handle.addListener(dbListener); handle.addListener(dbListener);
} }
@ -126,10 +124,7 @@ class DbViewerComponent extends JPanel {
synchronized void refreshTable() { synchronized void refreshTable() {
if (dbh == null) { if (dbh == null) {
if (southPanel != null) { removeWidgets();
remove(southPanel);
southPanel = null;
}
return; return;
} }
synchronized (dbh) { synchronized (dbh) {
@ -139,6 +134,7 @@ class DbViewerComponent extends JPanel {
synchronized void dispose() { synchronized void dispose() {
updateMgr.dispose(); updateMgr.dispose();
tableFilterPanel.dispose();
closeDatabase(); closeDatabase();
} }
@ -197,35 +193,20 @@ class DbViewerComponent extends JPanel {
private void updateTable() { private void updateTable() {
if (southPanel != null) { removeWidgets();
remove(southPanel);
southPanel = null;
}
TableItem t = (TableItem) combo.getSelectedItem(); TableItem t = (TableItem) combo.getSelectedItem();
if (t != null) { if (t != null) {
southPanel = createSouthPanel(t.table); centerPanel = createCenterPanel(t.table);
add(southPanel, BorderLayout.CENTER); add(centerPanel, BorderLayout.CENTER);
southComponent = createSouthComponent(t.table);
add(southComponent, BorderLayout.SOUTH);
} }
revalidate(); revalidate();
} }
private JPanel createSouthPanel(Table table) { private JComponent createSouthComponent(Table table) {
JPanel panel = new JPanel(new BorderLayout());
TableModel model = null;
GTable gTable = new GTable();
if (table.getRecordCount() <= 10000) {
model = new DbSmallTableModel(table);
}
else {
model = new DbLargeTableModel(table);
}
gTable.setModel(model);
gTable.setDefaultRenderer(Long.class, new LongRenderer());
JScrollPane scroll = new JScrollPane(gTable);
panel.add(scroll, BorderLayout.CENTER);
TableStatistics[] stats = getStats(table); TableStatistics[] stats = getStats(table);
String recCnt = "Records: " + Integer.toString(table.getRecordCount()); String recCnt = "Records: " + Integer.toString(table.getRecordCount());
String intNodeCnt = ""; String intNodeCnt = "";
@ -244,15 +225,22 @@ class DbViewerComponent extends JPanel {
size += " / " + Integer.toString(stats[1].size / 1024); size += " / " + Integer.toString(stats[1].size / 1024);
} }
} }
panel.add(new GLabel( return new GLabel(
recCnt + " " + intNodeCnt + " " + recNodeCnt + " " + chainBufCnt + " " + size), recCnt + " " + intNodeCnt + " " + recNodeCnt + " " + chainBufCnt + " " + size);
BorderLayout.SOUTH);
return panel;
} }
private DBListener getNewDBListener() { private JPanel createCenterPanel(Table table) {
return new InternalDBListener(); JPanel panel = new JPanel(new BorderLayout());
DbSmallTableModel model = new DbSmallTableModel(tool, table);
GThreadedTablePanel<DBRecord> threadedPanel = new GThreadedTablePanel<>(model);
GTable gTable = threadedPanel.getTable();
tableFilterPanel = new GTableFilterPanel<>(gTable, model);
panel.add(threadedPanel, BorderLayout.CENTER);
panel.add(tableFilterPanel, BorderLayout.SOUTH);
return panel;
} }
//================================================================================================== //==================================================================================================

View file

@ -41,7 +41,7 @@ import resources.Icons;
//@formatter:on //@formatter:on
public class DbViewerPlugin extends Plugin { public class DbViewerPlugin extends Plugin {
private DbViewerProvider viewer; private DbViewerProvider provider;
private DockingAction refreshAction; private DockingAction refreshAction;
public DbViewerPlugin(PluginTool tool) { public DbViewerPlugin(PluginTool tool) {
@ -52,11 +52,11 @@ public class DbViewerPlugin extends Plugin {
@Override @Override
protected void dispose() { protected void dispose() {
if (viewer != null) { if (provider != null) {
deactivateViewer(); deactivateViewer();
tool.removeComponentProvider(viewer); tool.removeComponentProvider(provider);
viewer.dispose(); provider.dispose();
viewer = null; provider = null;
} }
super.dispose(); super.dispose();
} }
@ -66,8 +66,8 @@ public class DbViewerPlugin extends Plugin {
refreshAction = new DockingAction("Refresh", getName()) { refreshAction = new DockingAction("Refresh", getName()) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
if (viewer != null) { if (provider != null) {
viewer.refresh(); provider.refresh();
} }
} }
}; };
@ -78,19 +78,19 @@ public class DbViewerPlugin extends Plugin {
} }
private void activateViewer(DomainObjectAdapterDB dobj) { private void activateViewer(DomainObjectAdapterDB dobj) {
if (viewer == null) { if (provider == null) {
viewer = new DbViewerProvider(this); provider = new DbViewerProvider(this);
tool.addComponentProvider(viewer, false); tool.addComponentProvider(provider, false);
tool.addLocalAction(viewer, refreshAction); tool.addLocalAction(provider, refreshAction);
} }
viewer.openDatabase(dobj.getName(), dobj.getDBHandle()); provider.openDatabase(dobj.getName(), dobj.getDBHandle());
refreshAction.setEnabled(true); refreshAction.setEnabled(true);
} }
private void deactivateViewer() { private void deactivateViewer() {
if (viewer != null) { if (provider != null) {
refreshAction.setEnabled(false); refreshAction.setEnabled(false);
viewer.closeDatabase(); provider.closeDatabase();
} }
} }

View file

@ -75,7 +75,7 @@ public class DbViewerProvider extends ComponentProviderAdapter {
@Override @Override
public JComponent getComponent() { public JComponent getComponent() {
if (comp == null) { if (comp == null) {
comp = new DbViewerComponent(); comp = new DbViewerComponent(tool);
if (dbh != null) { if (dbh != null) {
comp.openDatabase(dbName, dbh); comp.openDatabase(dbName, dbh);
} }

View file

@ -16,14 +16,51 @@
package ghidra.app.plugin.debug.dbtable; package ghidra.app.plugin.debug.dbtable;
import db.DBRecord; import db.DBRecord;
import docking.widgets.table.AbstractDynamicTableColumnStub;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
abstract class AbstractColumnAdapter { abstract class AbstractColumnAdapter extends AbstractDynamicTableColumnStub<DBRecord, Object> {
protected LongRenderer longRenderer = new LongRenderer();
protected int column;
private String columnName;
AbstractColumnAdapter(String columnName, int column) {
this.column = column;
this.columnName = columnName;
}
@Override
public Object getValue(DBRecord rowObject, Settings settings, ServiceProvider serviceProvider)
throws IllegalArgumentException {
if (column == 0) {
return getKeyValue(rowObject);
}
// -1, since the DB indices do not have the key column included
int dbColumn = column - 1;
return getValue(rowObject, dbColumn);
}
@SuppressWarnings("unchecked")
@Override
public Class<Object> getColumnClass() {
return (Class<Object>) getValueClass();
}
@Override
public String getColumnName() {
return columnName;
}
abstract Class<?> getValueClass(); abstract Class<?> getValueClass();
abstract Object getKeyValue(DBRecord rec); abstract Object getKeyValue(DBRecord rec);
abstract Object getValue(DBRecord rec, int col); abstract Object getValue(DBRecord rec, int dbColumn);
protected String getByteString(byte b) { protected String getByteString(byte b) {
String str = Integer.toHexString(b); String str = Integer.toHexString(b);
@ -33,20 +70,4 @@ abstract class AbstractColumnAdapter {
return "0x" + str; return "0x" + str;
} }
// private String format(long l, int size) {
// String hex = Long.toHexString(l);
// if (hex.length() > size) {
// hex = hex.substring(hex.length()-size);
// }
// else if (hex.length() < size) {
// StringBuffer b = new StringBuffer(20);
// for(int i=hex.length();i<size;i++) {
// b.append("");
// }
// b.append(hex);
// hex = b.toString();
// }
//
// return hex;
// }
} }

View file

@ -20,6 +20,10 @@ import db.DBRecord;
public class BinaryColumnAdapter extends AbstractColumnAdapter { public class BinaryColumnAdapter extends AbstractColumnAdapter {
BinaryColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override @Override
Class<?> getValueClass() { Class<?> getValueClass() {
return String.class; return String.class;
@ -28,7 +32,7 @@ public class BinaryColumnAdapter extends AbstractColumnAdapter {
@Override @Override
Object getKeyValue(DBRecord rec) { Object getKeyValue(DBRecord rec) {
byte[] bytes = ((BinaryField) rec.getKeyField()).getBinaryData(); byte[] bytes = ((BinaryField) rec.getKeyField()).getBinaryData();
StringBuffer buf = new StringBuffer(" byte[" + bytes.length + "] = "); StringBuffer buf = new StringBuffer("byte[" + bytes.length + "] = ");
if (bytes.length > 0) { if (bytes.length > 0) {
int len = Math.min(bytes.length, 20); int len = Math.min(bytes.length, 20);
buf.append(bytes[0]); buf.append(bytes[0]);
@ -49,7 +53,7 @@ public class BinaryColumnAdapter extends AbstractColumnAdapter {
if (bytes == null) { if (bytes == null) {
return "null"; return "null";
} }
StringBuffer buf = new StringBuffer(" byte[" + bytes.length + "] = "); StringBuilder buf = new StringBuilder("byte[" + bytes.length + "] = ");
if (bytes.length > 0) { if (bytes.length > 0) {
int len = Math.min(bytes.length, 20); int len = Math.min(bytes.length, 20);
String str = getByteString(bytes[0]); String str = getByteString(bytes[0]);

View file

@ -17,9 +17,23 @@ package ghidra.app.plugin.debug.dbtable;
import db.BooleanField; import db.BooleanField;
import db.DBRecord; import db.DBRecord;
import docking.widgets.table.GBooleanCellRenderer;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.GColumnRenderer;
public class BooleanColumnAdapter extends AbstractColumnAdapter { public class BooleanColumnAdapter extends AbstractColumnAdapter {
private BooleanRenderer renderer = new BooleanRenderer();
BooleanColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
public int getColumnPreferredWidth() {
return 75;
}
@Override @Override
Class<?> getValueClass() { Class<?> getValueClass() {
return Boolean.class; return Boolean.class;
@ -31,8 +45,23 @@ public class BooleanColumnAdapter extends AbstractColumnAdapter {
} }
@Override @Override
Object getValue(DBRecord rec, int col) { Object getValue(DBRecord rec, int dbColumn) {
return Boolean.valueOf(rec.getBooleanValue(col)); return Boolean.valueOf(rec.getBooleanValue(dbColumn));
} }
@Override
public BooleanRenderer getColumnRenderer() {
return renderer;
}
private class BooleanRenderer extends GBooleanCellRenderer implements GColumnRenderer<Object> {
@Override
public String getFilterString(Object t, Settings settings) {
Boolean b = (Boolean) t;
if (b == null) {
return Boolean.FALSE.toString();
}
return b.toString();
}
}
} }

View file

@ -20,6 +20,15 @@ import db.DBRecord;
public class ByteColumnAdapter extends AbstractColumnAdapter { public class ByteColumnAdapter extends AbstractColumnAdapter {
ByteColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
public int getColumnPreferredWidth() {
return 100;
}
@Override @Override
Class<?> getValueClass() { Class<?> getValueClass() {
return Byte.class; return Byte.class;
@ -31,8 +40,12 @@ public class ByteColumnAdapter extends AbstractColumnAdapter {
} }
@Override @Override
Object getValue(DBRecord rec, int col) { Object getValue(DBRecord rec, int dbColumn) {
return Byte.valueOf(rec.getByteValue(col)); return Byte.valueOf(rec.getByteValue(dbColumn));
} }
@Override
public LongRenderer getColumnRenderer() {
return longRenderer;
}
} }

View file

@ -1,274 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.debug.dbtable;
import java.io.IOException;
import java.util.*;
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>();
private Table table;
private Schema schema;
private List<AbstractColumnAdapter> columns = new ArrayList<AbstractColumnAdapter>();
private RecordIterator recIt;
private DBRecord lastRecord;
private int lastIndex;
private Field minKey;
private Field maxKey;
private Field keyType;
public DbLargeTableModel(Table table) {
this.table = table;
schema = table.getSchema();
try {
keyType = schema.getKeyFieldType();
}
catch (Exception e) {
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
}
try {
recIt = table.iterator();
lastRecord = recIt.next();
lastIndex = 0;
findMaxKey();
findMinKey();
}
catch (IOException e) {
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
}
columns.add(getColumn(schema.getKeyFieldType()));
Field[] fields = schema.getFields();
for (Field field : fields) {
columns.add(getColumn(field));
}
}
private AbstractColumnAdapter getColumn(Field field) {
if (field instanceof ByteField) {
return new ByteColumnAdapter();
}
else if (field instanceof BooleanField) {
return new BooleanColumnAdapter();
}
else if (field instanceof ShortField) {
return new ShortColumnAdapter();
}
else if (field instanceof IntField) {
return new IntegerColumnAdapter();
}
else if (field instanceof LongField) {
return new LongColumnAdapter();
}
else if (field instanceof StringField) {
return new StringColumnAdapter();
}
else if (field instanceof BinaryField) {
return new BinaryColumnAdapter();
}
throw new AssertException(
"New, unexpected DB column type: " + field.getClass().getSimpleName());
}
private void findMinKey() throws IOException {
RecordIterator iter = table.iterator();
DBRecord rec = iter.next();
minKey = rec.getKeyField();
}
private void findMaxKey() throws IOException {
Field max = keyType.newField();
if (table.useLongKeys()) {
max.setLongValue(Long.MAX_VALUE);
}
else {
byte[] maxBytes = new byte[128];
Arrays.fill(maxBytes, 0, 128, (byte) 0x7f);
max.setBinaryData(maxBytes);
}
RecordIterator iter = table.iterator(max);
DBRecord rec = iter.previous();
maxKey = rec.getKeyField();
}
@Override
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columns.get(columnIndex).getValueClass();
}
@Override
public int getColumnCount() {
return schema.getFieldCount() + 1;
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex == 0) {
return schema.getKeyName();
}
--columnIndex;
int[] indexCols = table.getIndexedColumns();
boolean isIndexed = false;
for (int i = 0; i < indexCols.length; i++) {
if (indexCols[i] == columnIndex) {
isIndexed = true;
break;
}
}
return schema.getFieldNames()[columnIndex] + (isIndexed ? "*" : "");
}
@Override
public int getRowCount() {
return table.getRecordCount();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
DBRecord rec = getRecord(rowIndex);
if (columnIndex == 0) { // key column
return columns.get(columnIndex).getKeyValue(rec);
}
int dbColumn = columnIndex - 1; // -1, since the DB indices do not have the key column included
return columns.get(columnIndex).getValue(rec, dbColumn);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// no!
}
private DBRecord getRecord(int index) {
try {
if (index == lastIndex + 1) {
if (recIt.hasNext()) {
lastRecord = recIt.next();
lastIndex = index;
}
else {
// iterator ran out
}
}
else if (index != lastIndex) {
if (index < lastIndex && (lastIndex - index) < 200) {
int backup = lastIndex - index + 1;
for (int i = 0; i < backup; i++) {
if (recIt.hasPrevious()) {
recIt.previous();
}
}
DBRecord rec = recIt.next();
if (rec != null) {
lastRecord = rec;
lastIndex = index;
}
}
else {
findRecord(index);
lastRecord = recIt.next();
lastIndex = index;
}
}
}
catch (IOException e) {
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
}
return lastRecord;
}
private void findRecord(int index) throws IOException {
if (index < 1000) {
recIt = table.iterator();
for (int i = 0; i < index; i++) {
recIt.next();
}
}
else if (index > table.getRecordCount() - 1000) {
recIt = table.iterator(maxKey);
if (recIt.hasNext()) {
recIt.next();
}
for (int i = 0; i < table.getRecordCount() - index; i++) {
recIt.previous();
}
}
else {
recIt = table.iterator(approxKey(index));
}
}
private Field approxKey(int index) {
Field key = keyType.newField();
if (table.useLongKeys()) {
long min = minKey.getLongValue();
long max = maxKey.getLongValue();
long k = min + ((max - min) * index / table.getRecordCount());
key.setLongValue(k);
}
else {
long min = getLong(minKey.getBinaryData());
long max = getLong(maxKey.getBinaryData());
long k = min + ((max - min) * index / table.getRecordCount());
byte[] bytes = new byte[8];
for (int i = 7; i >= 0; i--) {
bytes[i] = (byte) k;
k >>= 8;
}
key.setBinaryData(bytes);
}
return key;
}
private long getLong(byte[] bytes) {
if (bytes == null || bytes.length == 0)
return 0;
long value = 0;
for (int i = 0; i < 8; i++) {
value <<= 8;
if (i < bytes.length) {
value += bytes[i] & 0xff;
}
}
return value;
}
}

View file

@ -16,37 +16,40 @@
package ghidra.app.plugin.debug.dbtable; package ghidra.app.plugin.debug.dbtable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import db.*; import db.*;
import docking.widgets.table.AbstractSortedTableModel; import docking.widgets.table.TableColumnDescriptor;
import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class DbSmallTableModel extends AbstractSortedTableModel<DBRecord> { public class DbSmallTableModel extends ThreadedTableModel<DBRecord, Object> {
private Table table; private Table table;
private Schema schema; private Schema schema;
private List<AbstractColumnAdapter> columns = new ArrayList<>();
private List<DBRecord> records;
public DbSmallTableModel(Table table) { public DbSmallTableModel(ServiceProvider serviceProvider, Table table) {
super("DB Records Model", serviceProvider);
this.table = table; this.table = table;
schema = table.getSchema(); schema = table.getSchema();
records = new ArrayList<>(table.getRecordCount()); reloadColumns(); // we must do this after 'schema' has been set
}
columns.add(getColumn(schema.getKeyFieldType())); @Override
protected void doLoad(Accumulator<DBRecord> accumulator, TaskMonitor monitor)
throws CancelledException {
Field[] fields = schema.getFields(); monitor.initialize(table.getRecordCount());
for (Field field : fields) {
columns.add(getColumn(field));
}
try { try {
RecordIterator it = table.iterator(); RecordIterator it = table.iterator();
while (it.hasNext()) { while (it.hasNext()) {
records.add(it.next()); monitor.checkCancelled();
accumulator.add(it.next());
} }
} }
catch (IOException e) { catch (IOException e) {
@ -54,50 +57,35 @@ public class DbSmallTableModel extends AbstractSortedTableModel<DBRecord> {
} }
} }
private AbstractColumnAdapter getColumn(Field field) { private AbstractColumnAdapter getColumn(Field field, int column) {
String columnName = loadColumnName(column);
if (field instanceof ByteField) { if (field instanceof ByteField) {
return new ByteColumnAdapter(); return new ByteColumnAdapter(columnName, column);
} }
else if (field instanceof BooleanField) { else if (field instanceof BooleanField) {
return new BooleanColumnAdapter(); return new BooleanColumnAdapter(columnName, column);
} }
else if (field instanceof ShortField) { else if (field instanceof ShortField) {
return new ShortColumnAdapter(); return new ShortColumnAdapter(columnName, column);
} }
else if (field instanceof IntField) { else if (field instanceof IntField) {
return new IntegerColumnAdapter(); return new IntegerColumnAdapter(columnName, column);
} }
else if (field instanceof LongField) { else if (field instanceof LongField) {
return new LongColumnAdapter(); return new LongColumnAdapter(columnName, column);
} }
else if (field instanceof StringField) { else if (field instanceof StringField) {
return new StringColumnAdapter(); return new StringColumnAdapter(columnName, column);
} }
else if (field instanceof BinaryField) { else if (field instanceof BinaryField) {
return new BinaryColumnAdapter(); return new BinaryColumnAdapter(columnName, column);
} }
throw new AssertException( throw new AssertException(
"New, unexpected DB column type: " + field.getClass().getSimpleName()); "New, unexpected DB column type: " + field.getClass().getSimpleName());
} }
@Override private String loadColumnName(int columnIndex) {
public String getName() {
return "DB Small Table";
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columns.get(columnIndex).getValueClass();
}
@Override
public int getColumnCount() {
return schema.getFieldCount() + 1;
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex == 0) { if (columnIndex == 0) {
return schema.getKeyName(); return schema.getKeyName();
} }
@ -114,8 +102,23 @@ public class DbSmallTableModel extends AbstractSortedTableModel<DBRecord> {
} }
@Override @Override
public int getRowCount() { protected TableColumnDescriptor<DBRecord> createTableColumnDescriptor() {
return table.getRecordCount();
TableColumnDescriptor<DBRecord> descriptor = new TableColumnDescriptor<>();
if (schema == null) {
return descriptor;
}
// 0 is the key
descriptor.addVisibleColumn(getColumn(schema.getKeyFieldType(), 0));
Field[] fields = schema.getFields();
int offset = 1;
for (Field field : fields) {
descriptor.addVisibleColumn(getColumn(field, offset++));
}
return descriptor;
} }
@Override @Override
@ -124,22 +127,7 @@ public class DbSmallTableModel extends AbstractSortedTableModel<DBRecord> {
} }
@Override @Override
public Object getColumnValueForRow(DBRecord rec, int columnIndex) { public Object getDataSource() {
if (columnIndex == 0) { // key column return null;
return columns.get(columnIndex).getKeyValue(rec);
}
int dbColumn = columnIndex - 1; // -1, since the DB indices do not have the key column included
return columns.get(columnIndex).getValue(rec, dbColumn);
}
@Override
public List<DBRecord> getModelData() {
return records;
}
@Override
public boolean isSortable(int columnIndex) {
return true;
} }
} }

View file

@ -15,11 +15,20 @@
*/ */
package ghidra.app.plugin.debug.dbtable; package ghidra.app.plugin.debug.dbtable;
import db.IntField;
import db.DBRecord; import db.DBRecord;
import db.IntField;
public class IntegerColumnAdapter extends AbstractColumnAdapter { public class IntegerColumnAdapter extends AbstractColumnAdapter {
IntegerColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
@Override @Override
Class<?> getValueClass() { Class<?> getValueClass() {
return Integer.class; return Integer.class;
@ -35,4 +44,8 @@ public class IntegerColumnAdapter extends AbstractColumnAdapter {
return Integer.valueOf(rec.getIntValue(col)); return Integer.valueOf(rec.getIntValue(col));
} }
@Override
public LongRenderer getColumnRenderer() {
return longRenderer;
}
} }

View file

@ -19,6 +19,10 @@ import db.DBRecord;
public class LongColumnAdapter extends AbstractColumnAdapter { public class LongColumnAdapter extends AbstractColumnAdapter {
LongColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override @Override
Class<?> getValueClass() { Class<?> getValueClass() {
return Long.class; return Long.class;
@ -33,4 +37,9 @@ public class LongColumnAdapter extends AbstractColumnAdapter {
Object getValue(DBRecord rec, int col) { Object getValue(DBRecord rec, int col) {
return Long.valueOf(rec.getLongValue(col)); return Long.valueOf(rec.getLongValue(col));
} }
@Override
public LongRenderer getColumnRenderer() {
return longRenderer;
}
} }

View file

@ -20,11 +20,11 @@ import java.awt.Component;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import docking.widgets.table.GTableCellRenderer;
import docking.widgets.table.GTableCellRenderingData; import docking.widgets.table.GTableCellRenderingData;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class LongRenderer extends GTableCellRenderer { public class LongRenderer extends AbstractGColumnRenderer<Object> {
@Override @Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -38,11 +38,17 @@ public class LongRenderer extends GTableCellRenderer {
@Override @Override
protected String getText(Object value) { protected String getText(Object value) {
return value == null ? "" : "0x" + Long.toHexString((Long) value); return value == null ? "" : "0x" + Long.toHexString(((Number) value).longValue());
} }
@Override @Override
protected String formatNumber(Number value, Settings settings) { protected String formatNumber(Number value, Settings settings) {
return getText(value); return getText(value);
} }
@Override
public String getFilterString(Object t, Settings settings) {
// have the filter text match the display string
return getText(t);
}
} }

View file

@ -20,6 +20,10 @@ import db.ShortField;
public class ShortColumnAdapter extends AbstractColumnAdapter { public class ShortColumnAdapter extends AbstractColumnAdapter {
ShortColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override @Override
Class<?> getValueClass() { Class<?> getValueClass() {
return Short.class; return Short.class;
@ -35,4 +39,8 @@ public class ShortColumnAdapter extends AbstractColumnAdapter {
return Short.valueOf(rec.getShortValue(col)); return Short.valueOf(rec.getShortValue(col));
} }
@Override
public LongRenderer getColumnRenderer() {
return longRenderer;
}
} }

View file

@ -20,6 +20,10 @@ import db.StringField;
public class StringColumnAdapter extends AbstractColumnAdapter { public class StringColumnAdapter extends AbstractColumnAdapter {
StringColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override @Override
Class<?> getValueClass() { Class<?> getValueClass() {
return String.class; return String.class;
@ -32,6 +36,6 @@ public class StringColumnAdapter extends AbstractColumnAdapter {
@Override @Override
Object getValue(DBRecord rec, int col) { Object getValue(DBRecord rec, int col) {
return " " + rec.getString(col); return rec.getString(col);
} }
} }

View file

@ -16,12 +16,10 @@
package db; package db;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.*;
import java.io.IOException;
import java.util.*; import java.util.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.TableModel;
import db.buffers.LocalBufferFile; import db.buffers.LocalBufferFile;
import docking.framework.DockingApplicationConfiguration; import docking.framework.DockingApplicationConfiguration;
@ -30,10 +28,12 @@ import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode; import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel; import docking.widgets.label.GLabel;
import docking.widgets.table.GTable;
import docking.widgets.table.GTableFilterPanel;
import generic.application.GenericApplicationLayout; import generic.application.GenericApplicationLayout;
import ghidra.app.plugin.debug.dbtable.DbLargeTableModel;
import ghidra.app.plugin.debug.dbtable.DbSmallTableModel; import ghidra.app.plugin.debug.dbtable.DbSmallTableModel;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.framework.store.db.PackedDatabase; import ghidra.framework.store.db.PackedDatabase;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.filechooser.ExtensionFileFilter; import ghidra.util.filechooser.ExtensionFileFilter;
@ -56,6 +56,7 @@ public class DbViewer extends JFrame {
private JComboBox<String> combo; private JComboBox<String> combo;
private Table[] tables; private Table[] tables;
private Hashtable<String, TableStatistics[]> tableStats = new Hashtable<>(); private Hashtable<String, TableStatistics[]> tableStats = new Hashtable<>();
private GTableFilterPanel<DBRecord> tableFilterPanel;
DbViewer() { DbViewer() {
super("Database Viewer"); super("Database Viewer");
@ -177,17 +178,13 @@ public class DbViewer extends JFrame {
private JPanel createSouthPanel(Table table) { private JPanel createSouthPanel(Table table) {
JPanel panel = new JPanel(new BorderLayout()); JPanel panel = new JPanel(new BorderLayout());
TableModel model = null; DbSmallTableModel model = new DbSmallTableModel(new ServiceProviderStub(), table);
if (table.getRecordCount() <= 10000) { GTable gTable = new GTable(model);
model = new DbSmallTableModel(table);
}
else {
model = new DbLargeTableModel(table);
}
JTable jtable = new JTable(model);
JScrollPane scroll = new JScrollPane(jtable); tableFilterPanel = new GTableFilterPanel<>(gTable, model);
JScrollPane scroll = new JScrollPane(gTable);
panel.add(scroll, BorderLayout.CENTER); panel.add(scroll, BorderLayout.CENTER);
panel.add(tableFilterPanel, BorderLayout.SOUTH);
TableStatistics[] stats = getStats(table); TableStatistics[] stats = getStats(table);
String recCnt = "Records: " + Integer.toString(table.getRecordCount()); String recCnt = "Records: " + Integer.toString(table.getRecordCount());
@ -216,10 +213,9 @@ public class DbViewer extends JFrame {
/** /**
* Get the statistics for the specified table. * Get the statistics for the specified table.
* @param table * @param table the table
* @return arrays containing statistics. Element 0 provides * @return arrays containing statistics. Element 0 provides statistics for primary table,
* statsitics for primary table, element 1 provides combined * element 1 provides combined statistics for all index tables. Remaining array elements
* statsitics for all index tables. Remaining array elements
* should be ignored since they have been combined into element 1. * should be ignored since they have been combined into element 1.
*/ */
private TableStatistics[] getStats(Table table) { private TableStatistics[] getStats(Table table) {
@ -238,39 +234,21 @@ public class DbViewer extends JFrame {
tableStats.put(table.getName(), stats); tableStats.put(table.getName(), stats);
} }
catch (IOException e) { catch (IOException e) {
Msg.error(this, "Exception loading stats", e);
} }
} }
return stats; return stats;
} }
/** public static void main(String[] args) throws FileNotFoundException {
* Launch the DbViewer application.
* @param args (not used)
*/
public static void main(String[] args) throws IOException {
ApplicationLayout layout = new GenericApplicationLayout("DB Viewer", "1.0"); ApplicationLayout layout = new GenericApplicationLayout("DB Viewer", "1.0");
DockingApplicationConfiguration configuration = new DockingApplicationConfiguration(); DockingApplicationConfiguration configuration = new DockingApplicationConfiguration();
configuration.setShowSplashScreen(false); configuration.setShowSplashScreen(false);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (ClassNotFoundException e) {
}
catch (InstantiationException e) {
}
catch (IllegalAccessException e) {
}
catch (UnsupportedLookAndFeelException e) {
}
Application.initializeApplication(layout, configuration); Application.initializeApplication(layout, configuration);
DbViewer viewer = new DbViewer(); DbViewer viewer = new DbViewer();
viewer.setSize(new Dimension(500, 400)); viewer.setSize(new Dimension(500, 400));
viewer.setVisible(true); viewer.setVisible(true);
} }
} }

View file

@ -293,7 +293,6 @@ public class FidDebugPlugin extends ProgramPlugin implements ChangeListener {
* @param choices array of choices for the users * @param choices array of choices for the users
* @param defaultValue the default value to select * @param defaultValue the default value to select
* @return the user's choice, or null * @return the user's choice, or null
* @throws CancelledException if the user cancels
*/ */
protected <T> T askChoice(String title, String message, List<T> choices, T defaultValue) { protected <T> T askChoice(String title, String message, List<T> choices, T defaultValue) {
AskDialog<T> dialog = AskDialog<T> dialog =

View file

@ -23,6 +23,8 @@ import ghidra.framework.plugintool.ServiceProvider;
* the DATA_SOURCE parameter of DynamicTableColumn. This class will stub the default * the DATA_SOURCE parameter of DynamicTableColumn. This class will stub the default
* {@link #getValue(Object, Settings, Object, ServiceProvider)} method and * {@link #getValue(Object, Settings, Object, ServiceProvider)} method and
* call a version of the method that does not have the DATA_SOURCE parameter. * call a version of the method that does not have the DATA_SOURCE parameter.
* @param <ROW_TYPE> the row type
* @param <COLUMN_TYPE> the column type
*/ */
public abstract class AbstractDynamicTableColumnStub<ROW_TYPE, COLUMN_TYPE> extends public abstract class AbstractDynamicTableColumnStub<ROW_TYPE, COLUMN_TYPE> extends
AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object> { AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object> {

View file

@ -15,13 +15,12 @@
*/ */
package ghidra.util; package ghidra.util;
import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.math.BigInteger;
import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -58,34 +57,39 @@ public final class NumericUtilities {
} }
/** /**
* Parses the given string as a numeric value, detecting whether or not it begins with a Hex * Parses the given string as a numeric value, detecting whether or not it begins with a hex
* prefix, and if not, parses as a long int value. * prefix, and if not, parses as a long int value.
* *
* @param numStr the number string * @param numStr the number string
* @return the long value or 0 * @return the long value or 0
*
*/ */
public static long parseNumber(String numStr) { public static long parseNumber(String numStr) {
return parseNumber(numStr, Long.valueOf(0)); return parseNumber(numStr, Long.valueOf(0));
} }
public static Long parseNumber(String numStr, Long defaultValue) { /**
* Parses the given string as a numeric value, detecting whether or not it begins with a hex
numStr = (numStr == null ? "" : numStr.trim()); * prefix, and if not, parses as a long int value.
if (numStr.length() == 0) { * @param s the string to parse
* @param defaultValue the default value to use if the string cannot be parsed
* @return the long value
*/
public static Long parseNumber(String s, Long defaultValue) {
s = (s == null ? "" : s.trim());
if (s.length() == 0) {
return defaultValue; return defaultValue;
} }
long value = 0; long value = 0;
try { try {
if (numStr.startsWith(HEX_PREFIX_x) || numStr.startsWith(HEX_PREFIX_X)) { if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
value = Integer.parseInt(numStr.substring(2), 16); value = Integer.parseInt(s.substring(2), 16);
} }
else { else {
value = Integer.parseInt(numStr); value = Integer.parseInt(s);
} }
} }
catch (NumberFormatException exc) { catch (NumberFormatException e) {
// do nothing special; use default value // do nothing special; use default value
return defaultValue; return defaultValue;
} }
@ -94,41 +98,43 @@ public final class NumericUtilities {
} }
/** /**
* parses the given string as a numeric value, detecting whether or not it begins with a Hex * Parses the given string as a numeric value, detecting whether or not it begins with a hex
* prefix, and if not, parses as a long int value. * prefix, and if not, parses as a long int value.
* @param s the string to parse
* @return the long value
* @throws NumberFormatException if the string is blank or has too many digits
*/ */
public static long parseLong(String numStr) { public static long parseLong(String s) {
String origStr = numStr; String origStr = s;
long value = 0; long value = 0;
long sign = 1; long sign = 1;
numStr = (numStr == null ? "" : numStr.trim()); s = (s == null ? "" : s.trim());
if (numStr.length() == 0) { if (s.length() == 0) {
return value; return value;
} }
if (numStr.startsWith("-")) { if (s.startsWith("-")) {
sign = -1; sign = -1;
numStr = numStr.substring(1); s = s.substring(1);
} }
int radix = 10; int radix = 10;
if (numStr.startsWith(HEX_PREFIX_x) || numStr.startsWith(HEX_PREFIX_X)) { if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
if (numStr.length() > 18) { if (s.length() > 18) {
throw new NumberFormatException(numStr + " has too many digits."); throw new NumberFormatException(s + " has too many digits.");
} }
numStr = numStr.substring(2); s = s.substring(2);
radix = 16; radix = 16;
} }
if (numStr.length() == 0) { if (s.length() == 0) {
return 0; return 0;
} }
try { try {
BigInteger bi = new BigInteger(numStr, radix); BigInteger bi = new BigInteger(s, radix);
return bi.longValue() * sign; return bi.longValue() * sign;
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
// This is a little hacky, but the message should be complete and report about the // A little hacky, but the message should be complete and report the original string
// original string
NumberFormatException e2 = NumberFormatException e2 =
new NumberFormatException("Cannot parse long from " + origStr); new NumberFormatException("Cannot parse long from " + origStr);
e2.setStackTrace(e.getStackTrace()); e2.setStackTrace(e.getStackTrace());
@ -140,81 +146,64 @@ public final class NumericUtilities {
} }
/** /**
* parses the given string as a numeric value, detecting whether or not it begins with a Hex * Parses the given string as a hex long value, detecting whether or not it begins with a hex
* prefix, and if not, parses as a long int value. * prefix, and if not, parses as a long int value.
* @param s the string to parse
* @return the long value
* @throws NumberFormatException if the string is blank
*/ */
public static long parseOctLong(String numStr) { public static long parseHexLong(String s) {
return parseHexBigInteger(s).longValue();
long value = 0;
long sign = 1;
numStr = (numStr == null ? "" : numStr.trim());
if (numStr.length() == 0) {
return value;
}
if (numStr.startsWith("-")) {
sign = -1;
numStr = numStr.substring(1);
}
int radix = 8;
if (numStr.startsWith("0")) {
if (numStr.length() > 18) {
throw new NumberFormatException(numStr + " has too many digits.");
}
numStr = numStr.substring(1);
}
BigInteger bi = new BigInteger(numStr, radix);
return bi.longValue() * sign;
}
public static long parseHexLong(String numStr) {
return parseHexBigInteger(numStr).longValue();
}
public static BigInteger parseHexBigInteger(String numStr) {
numStr = (numStr == null ? "" : numStr.trim());
if (numStr.length() == 0) {
throw new NumberFormatException(numStr + " no digits.");
}
boolean negative = false;
if (numStr.startsWith("-")) {
negative = true;
numStr = numStr.substring(1);
}
if (numStr.startsWith(HEX_PREFIX_x) || numStr.startsWith(HEX_PREFIX_X)) {
numStr = numStr.substring(2);
}
if (negative) {
numStr = "-" + numStr;
}
return new BigInteger(numStr, 16);
} }
/** /**
* returns the value of the specified long as hexadecimal, prefixing with the HEX_PREFIX_x * Parses the given hex string as a BigIntge value, detecting whether or not it begins with a
* string. * hex prefix, and if not, parses as a long int value.
* @param s the string to parse
* @return the long value
* @throws NumberFormatException if the string is blank
*/
public static BigInteger parseHexBigInteger(String s) {
s = (s == null ? "" : s.trim());
if (s.length() == 0) {
throw new NumberFormatException(s + " no digits.");
}
boolean negative = false;
if (s.startsWith("-")) {
negative = true;
s = s.substring(1);
}
if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
s = s.substring(2);
}
if (negative) {
s = "-" + s;
}
return new BigInteger(s, 16);
}
/**
* returns the value of the specified long as hexadecimal, prefixing with the
* {@link #HEX_PREFIX_x} string.
* *
* @param value the long value to convert * @param value the long value to convert
* @return the string
*/ */
public final static String toHexString(long value) { public final static String toHexString(long value) {
return HEX_PREFIX_x + Long.toHexString(value); return HEX_PREFIX_x + Long.toHexString(value);
} }
/** /**
* returns the value of the specified long as hexadecimal, prefixing with the HEX_PREFIX_x * returns the value of the specified long as hexadecimal, prefixing with the
* string. * {@link #HEX_PREFIX_x} string.
* *
* @param value the long value to convert * @param value the long value to convert
* @param size number of bytes to be represented * @param size number of bytes to be represented
* @return the string
*/ */
public final static String toHexString(long value, int size) { public final static String toHexString(long value, int size) {
if (size > 0 && size < 8) { if (size > 0 && size < 8) {
@ -225,9 +214,10 @@ public final class NumericUtilities {
/** /**
* returns the value of the specified long as signed hexadecimal, prefixing with the * returns the value of the specified long as signed hexadecimal, prefixing with the
* HEX_PREFIX_x string. * {@link #HEX_PREFIX_x} string.
* *
* @param value the long value to convert * @param value the long value to convert
* @return the string
*/ */
public final static String toSignedHexString(long value) { public final static String toSignedHexString(long value) {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();