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.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.*;
import javax.swing.*;
import javax.swing.table.TableModel;
import db.*;
import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
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.layout.PairLayout;
import ghidra.util.task.SwingUpdateManager;
@ -39,25 +39,27 @@ class DbViewerComponent extends JPanel {
private static Table[] NO_TABLES = new Table[0];
private static Comparator<Table> TABLE_NAME_COMPARATOR = new Comparator<>() {
@Override
public int compare(Table o1, Table o2) {
return (o1).getName().compareTo((o2).getName());
}
};
private static Comparator<Table> TABLE_NAME_COMPARATOR =
(o1, o2) -> (o1).getName().compareTo((o2).getName());
private DBHandle dbh;
private DBListener dbListener;
private JPanel southPanel;
private JPanel centerPanel;
private JComponent southComponent;
private JLabel dbLabel;
private JComboBox<TableItem> combo;
private Table[] tables = NO_TABLES;
private Hashtable<String, TableStatistics[]> tableStats = new Hashtable<>();
private Map<String, TableStatistics[]> tableStats = new HashMap<>();
private SwingUpdateManager updateMgr;
DbViewerComponent() {
private PluginTool tool;
private GTableFilterPanel<DBRecord> tableFilterPanel;
DbViewerComponent(PluginTool tool) {
super(new BorderLayout());
this.tool = tool;
JPanel northPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JPanel subNorthPanel = new JPanel(new PairLayout(4, 10));
@ -66,32 +68,19 @@ class DbViewerComponent extends JPanel {
subNorthPanel.add(dbLabel);
subNorthPanel.add(new GLabel("Tables:"));
combo = new GComboBox<>();
combo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
refreshTable();
}
});
combo.addActionListener(e -> refreshTable());
subNorthPanel.add(combo);
northPanel.add(subNorthPanel);
add(northPanel, BorderLayout.NORTH);
updateMgr = new SwingUpdateManager(100, 2000, new Runnable() {
@Override
public void run() {
refresh();
}
});
updateMgr = new SwingUpdateManager(100, 2000, () -> refresh());
}
synchronized void closeDatabase() {
if (dbh != null) {
combo.removeAllItems();
dbLabel.setText("");
if (southPanel != null) {
remove(southPanel);
southPanel = null;
}
removeWidgets();
tables = NO_TABLES;
tableStats.clear();
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) {
closeDatabase();
@ -110,7 +108,7 @@ class DbViewerComponent extends JPanel {
dbLabel.setText(name);
updateTableChoices(null);
dbListener = getNewDBListener();
dbListener = new InternalDBListener();
handle.addListener(dbListener);
}
@ -126,10 +124,7 @@ class DbViewerComponent extends JPanel {
synchronized void refreshTable() {
if (dbh == null) {
if (southPanel != null) {
remove(southPanel);
southPanel = null;
}
removeWidgets();
return;
}
synchronized (dbh) {
@ -139,6 +134,7 @@ class DbViewerComponent extends JPanel {
synchronized void dispose() {
updateMgr.dispose();
tableFilterPanel.dispose();
closeDatabase();
}
@ -197,35 +193,20 @@ class DbViewerComponent extends JPanel {
private void updateTable() {
if (southPanel != null) {
remove(southPanel);
southPanel = null;
}
removeWidgets();
TableItem t = (TableItem) combo.getSelectedItem();
if (t != null) {
southPanel = createSouthPanel(t.table);
add(southPanel, BorderLayout.CENTER);
centerPanel = createCenterPanel(t.table);
add(centerPanel, BorderLayout.CENTER);
southComponent = createSouthComponent(t.table);
add(southComponent, BorderLayout.SOUTH);
}
revalidate();
}
private JPanel createSouthPanel(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);
private JComponent createSouthComponent(Table table) {
TableStatistics[] stats = getStats(table);
String recCnt = "Records: " + Integer.toString(table.getRecordCount());
String intNodeCnt = "";
@ -244,15 +225,22 @@ class DbViewerComponent extends JPanel {
size += " / " + Integer.toString(stats[1].size / 1024);
}
}
panel.add(new GLabel(
recCnt + " " + intNodeCnt + " " + recNodeCnt + " " + chainBufCnt + " " + size),
BorderLayout.SOUTH);
return panel;
return new GLabel(
recCnt + " " + intNodeCnt + " " + recNodeCnt + " " + chainBufCnt + " " + size);
}
private DBListener getNewDBListener() {
return new InternalDBListener();
private JPanel createCenterPanel(Table table) {
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
public class DbViewerPlugin extends Plugin {
private DbViewerProvider viewer;
private DbViewerProvider provider;
private DockingAction refreshAction;
public DbViewerPlugin(PluginTool tool) {
@ -52,11 +52,11 @@ public class DbViewerPlugin extends Plugin {
@Override
protected void dispose() {
if (viewer != null) {
if (provider != null) {
deactivateViewer();
tool.removeComponentProvider(viewer);
viewer.dispose();
viewer = null;
tool.removeComponentProvider(provider);
provider.dispose();
provider = null;
}
super.dispose();
}
@ -66,8 +66,8 @@ public class DbViewerPlugin extends Plugin {
refreshAction = new DockingAction("Refresh", getName()) {
@Override
public void actionPerformed(ActionContext context) {
if (viewer != null) {
viewer.refresh();
if (provider != null) {
provider.refresh();
}
}
};
@ -78,19 +78,19 @@ public class DbViewerPlugin extends Plugin {
}
private void activateViewer(DomainObjectAdapterDB dobj) {
if (viewer == null) {
viewer = new DbViewerProvider(this);
tool.addComponentProvider(viewer, false);
tool.addLocalAction(viewer, refreshAction);
if (provider == null) {
provider = new DbViewerProvider(this);
tool.addComponentProvider(provider, false);
tool.addLocalAction(provider, refreshAction);
}
viewer.openDatabase(dobj.getName(), dobj.getDBHandle());
provider.openDatabase(dobj.getName(), dobj.getDBHandle());
refreshAction.setEnabled(true);
}
private void deactivateViewer() {
if (viewer != null) {
if (provider != null) {
refreshAction.setEnabled(false);
viewer.closeDatabase();
provider.closeDatabase();
}
}

View file

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

View file

@ -16,14 +16,51 @@
package ghidra.app.plugin.debug.dbtable;
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 Object getKeyValue(DBRecord rec);
abstract Object getValue(DBRecord rec, int col);
abstract Object getValue(DBRecord rec, int dbColumn);
protected String getByteString(byte b) {
String str = Integer.toHexString(b);
@ -33,20 +70,4 @@ abstract class AbstractColumnAdapter {
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 {
BinaryColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
Class<?> getValueClass() {
return String.class;
@ -49,7 +53,7 @@ public class BinaryColumnAdapter extends AbstractColumnAdapter {
if (bytes == null) {
return "null";
}
StringBuffer buf = new StringBuffer(" byte[" + bytes.length + "] = ");
StringBuilder buf = new StringBuilder("byte[" + bytes.length + "] = ");
if (bytes.length > 0) {
int len = Math.min(bytes.length, 20);
String str = getByteString(bytes[0]);

View file

@ -17,9 +17,23 @@ package ghidra.app.plugin.debug.dbtable;
import db.BooleanField;
import db.DBRecord;
import docking.widgets.table.GBooleanCellRenderer;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.GColumnRenderer;
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
Class<?> getValueClass() {
return Boolean.class;
@ -31,8 +45,23 @@ public class BooleanColumnAdapter extends AbstractColumnAdapter {
}
@Override
Object getValue(DBRecord rec, int col) {
return Boolean.valueOf(rec.getBooleanValue(col));
Object getValue(DBRecord rec, int dbColumn) {
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 {
ByteColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
public int getColumnPreferredWidth() {
return 100;
}
@Override
Class<?> getValueClass() {
return Byte.class;
@ -31,8 +40,12 @@ public class ByteColumnAdapter extends AbstractColumnAdapter {
}
@Override
Object getValue(DBRecord rec, int col) {
return Byte.valueOf(rec.getByteValue(col));
Object getValue(DBRecord rec, int dbColumn) {
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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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.datastruct.Accumulator;
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 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;
schema = table.getSchema();
records = new ArrayList<>(table.getRecordCount());
columns.add(getColumn(schema.getKeyFieldType()));
Field[] fields = schema.getFields();
for (Field field : fields) {
columns.add(getColumn(field));
reloadColumns(); // we must do this after 'schema' has been set
}
@Override
protected void doLoad(Accumulator<DBRecord> accumulator, TaskMonitor monitor)
throws CancelledException {
monitor.initialize(table.getRecordCount());
try {
RecordIterator it = table.iterator();
while (it.hasNext()) {
records.add(it.next());
monitor.checkCancelled();
accumulator.add(it.next());
}
}
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) {
return new ByteColumnAdapter();
return new ByteColumnAdapter(columnName, column);
}
else if (field instanceof BooleanField) {
return new BooleanColumnAdapter();
return new BooleanColumnAdapter(columnName, column);
}
else if (field instanceof ShortField) {
return new ShortColumnAdapter();
return new ShortColumnAdapter(columnName, column);
}
else if (field instanceof IntField) {
return new IntegerColumnAdapter();
return new IntegerColumnAdapter(columnName, column);
}
else if (field instanceof LongField) {
return new LongColumnAdapter();
return new LongColumnAdapter(columnName, column);
}
else if (field instanceof StringField) {
return new StringColumnAdapter();
return new StringColumnAdapter(columnName, column);
}
else if (field instanceof BinaryField) {
return new BinaryColumnAdapter();
return new BinaryColumnAdapter(columnName, column);
}
throw new AssertException(
"New, unexpected DB column type: " + field.getClass().getSimpleName());
}
@Override
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) {
private String loadColumnName(int columnIndex) {
if (columnIndex == 0) {
return schema.getKeyName();
}
@ -114,8 +102,23 @@ public class DbSmallTableModel extends AbstractSortedTableModel<DBRecord> {
}
@Override
public int getRowCount() {
return table.getRecordCount();
protected TableColumnDescriptor<DBRecord> createTableColumnDescriptor() {
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
@ -124,22 +127,7 @@ public class DbSmallTableModel extends AbstractSortedTableModel<DBRecord> {
}
@Override
public Object getColumnValueForRow(DBRecord rec, int columnIndex) {
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 List<DBRecord> getModelData() {
return records;
}
@Override
public boolean isSortable(int columnIndex) {
return true;
public Object getDataSource() {
return null;
}
}

View file

@ -15,11 +15,20 @@
*/
package ghidra.app.plugin.debug.dbtable;
import db.IntField;
import db.DBRecord;
import db.IntField;
public class IntegerColumnAdapter extends AbstractColumnAdapter {
IntegerColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
public int getColumnPreferredWidth() {
return 200;
}
@Override
Class<?> getValueClass() {
return Integer.class;
@ -35,4 +44,8 @@ public class IntegerColumnAdapter extends AbstractColumnAdapter {
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 {
LongColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
Class<?> getValueClass() {
return Long.class;
@ -33,4 +37,9 @@ public class LongColumnAdapter extends AbstractColumnAdapter {
Object getValue(DBRecord rec, int 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.SwingConstants;
import docking.widgets.table.GTableCellRenderer;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class LongRenderer extends GTableCellRenderer {
public class LongRenderer extends AbstractGColumnRenderer<Object> {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
@ -38,11 +38,17 @@ public class LongRenderer extends GTableCellRenderer {
@Override
protected String getText(Object value) {
return value == null ? "" : "0x" + Long.toHexString((Long) value);
return value == null ? "" : "0x" + Long.toHexString(((Number) value).longValue());
}
@Override
protected String formatNumber(Number value, Settings settings) {
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 {
ShortColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
Class<?> getValueClass() {
return Short.class;
@ -35,4 +39,8 @@ public class ShortColumnAdapter extends AbstractColumnAdapter {
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 {
StringColumnAdapter(String columnName, int column) {
super(columnName, column);
}
@Override
Class<?> getValueClass() {
return String.class;
@ -32,6 +36,6 @@ public class StringColumnAdapter extends AbstractColumnAdapter {
@Override
Object getValue(DBRecord rec, int col) {
return " " + rec.getString(col);
return rec.getString(col);
}
}

View file

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

View file

@ -15,13 +15,12 @@
*/
package ghidra.util;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.math.BigInteger;
import org.apache.commons.collections4.IteratorUtils;
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.
*
* @param numStr the number string
* @return the long value or 0
*
*/
public static long parseNumber(String numStr) {
return parseNumber(numStr, Long.valueOf(0));
}
public static Long parseNumber(String numStr, Long defaultValue) {
numStr = (numStr == null ? "" : numStr.trim());
if (numStr.length() == 0) {
/**
* 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.
* @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;
}
long value = 0;
try {
if (numStr.startsWith(HEX_PREFIX_x) || numStr.startsWith(HEX_PREFIX_X)) {
value = Integer.parseInt(numStr.substring(2), 16);
if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
value = Integer.parseInt(s.substring(2), 16);
}
else {
value = Integer.parseInt(numStr);
value = Integer.parseInt(s);
}
}
catch (NumberFormatException exc) {
catch (NumberFormatException e) {
// do nothing special; use default value
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.
* @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) {
String origStr = numStr;
public static long parseLong(String s) {
String origStr = s;
long value = 0;
long sign = 1;
numStr = (numStr == null ? "" : numStr.trim());
if (numStr.length() == 0) {
s = (s == null ? "" : s.trim());
if (s.length() == 0) {
return value;
}
if (numStr.startsWith("-")) {
if (s.startsWith("-")) {
sign = -1;
numStr = numStr.substring(1);
s = s.substring(1);
}
int radix = 10;
if (numStr.startsWith(HEX_PREFIX_x) || numStr.startsWith(HEX_PREFIX_X)) {
if (numStr.length() > 18) {
throw new NumberFormatException(numStr + " has too many digits.");
if (s.startsWith(HEX_PREFIX_x) || s.startsWith(HEX_PREFIX_X)) {
if (s.length() > 18) {
throw new NumberFormatException(s + " has too many digits.");
}
numStr = numStr.substring(2);
s = s.substring(2);
radix = 16;
}
if (numStr.length() == 0) {
if (s.length() == 0) {
return 0;
}
try {
BigInteger bi = new BigInteger(numStr, radix);
BigInteger bi = new BigInteger(s, radix);
return bi.longValue() * sign;
}
catch (NumberFormatException e) {
// This is a little hacky, but the message should be complete and report about the
// original string
// A little hacky, but the message should be complete and report the original string
NumberFormatException e2 =
new NumberFormatException("Cannot parse long from " + origStr);
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.
* @param s the string to parse
* @return the long value
* @throws NumberFormatException if the string is blank
*/
public static long parseOctLong(String numStr) {
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);
public static long parseHexLong(String s) {
return parseHexBigInteger(s).longValue();
}
/**
* returns the value of the specified long as hexadecimal, prefixing with the HEX_PREFIX_x
* string.
* Parses the given hex string as a BigIntge value, detecting whether or not it begins with a
* 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
* @return the string
*/
public final static String toHexString(long value) {
return HEX_PREFIX_x + Long.toHexString(value);
}
/**
* returns the value of the specified long as hexadecimal, prefixing with the HEX_PREFIX_x
* string.
* 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 size number of bytes to be represented
* @return the string
*/
public final static String toHexString(long value, int size) {
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
* HEX_PREFIX_x string.
* {@link #HEX_PREFIX_x} string.
*
* @param value the long value to convert
* @return the string
*/
public final static String toSignedHexString(long value) {
StringBuffer buf = new StringBuffer();