GP-1382 - Added Find actions to the Data Types provider to allow users

to find structures by size or by offset(s).
This commit is contained in:
dragonmacher 2021-10-20 16:54:04 -04:00
parent f9463e600d
commit ec88c732d2
19 changed files with 1104 additions and 379 deletions

View file

@ -754,6 +754,26 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
return provider;
}
/**
* Allows you to find a component provider <b>with the given title</b>. Most plugins will
* only ever have a single provider. In those cases, use
* {@link #waitForComponentProvider(Class)}. This version of that method is to allow you to
* differentiate between multiple instances of a given provider that have different titles.
*
* @param clazz The class of the ComponentProvider to locate
* @param title the title of the component provider
* @return The component provider, or null if one cannot be found
*/
public static <T extends ComponentProvider> T waitForComponentProvider(Class<T> clazz,
String title) {
DockingWindowManager dwm = findActiveDockingWindowManager();
assertNotNull("Unable to find a DockingWindowManager - is there a tool showing?", dwm);
T provider = doWaitForComponentProvider(dwm, clazz, title);
return provider;
}
@SuppressWarnings("unchecked")
private static DockingWindowManager findActiveDockingWindowManager() {
DockingWindowManager activeInstance = DockingWindowManager.getActiveInstance();
@ -795,6 +815,25 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
"Timed-out waiting for ComponentProvider of class: " + clazz);
}
private static <T extends ComponentProvider> T doWaitForComponentProvider(
DockingWindowManager windowManager, Class<T> clazz, String title) {
Objects.requireNonNull(windowManager, "DockingWindowManager cannot be null");
int totalTime = 0;
while (totalTime <= DEFAULT_WAIT_TIMEOUT) {
T t = getComponentProvider(windowManager, clazz);
if (Objects.deepEquals(title, t.getTitle())) {
return t;
}
totalTime += sleep(DEFAULT_WAIT_DELAY);
}
throw new AssertionFailedError(
"Timed-out waiting for ComponentProvider of class: " + clazz);
}
/** These providers are those that appear in dialogs outside of the main frame **/
private static <T extends ComponentProvider> T getDetachedWindowProvider(
final Class<T> providerClass, final DockingWindowManager windowManager) {

View file

@ -0,0 +1,252 @@
/* ###
* 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 docking.widgets.dialogs;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.widgets.label.GLabel;
import docking.widgets.textfield.HintTextField;
import ghidra.util.NumericUtilities;
import ghidra.util.datastruct.SortedRangeList;
import ghidra.util.layout.PairLayout;
/**
* An input dialog that accepts number input as discrete values or a range of values using
* ':' as the range separator.
*/
public class NumberRangeInputDialog extends DialogComponentProvider {
private static final String RANGE_DELIMITER = ":";
private static final String DEFAULT_VALUE = "";
private static final String HINT_TEXT = "e.g. 2,5 or 1,4:8";
private static final int MAX_SIZE = 256;
private boolean wasCancelled;
private String inputLabel;
private String initialValue = DEFAULT_VALUE;
private SortedRangeList rangeList = new SortedRangeList();
private HintTextField textField;
private KeyListener keyListener;
public NumberRangeInputDialog(String title, String label) {
super(title, true, true/* status */, true /* buttons */,
false /* no tasks */);
keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ENTER) {
okCallback();
}
}
};
this.inputLabel = label;
setTransient(true);
addOKButton();
addCancelButton();
buildMainPanel();
DocumentListener docListener = new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
clearStatusText();
}
@Override
public void insertUpdate(DocumentEvent e) {
clearStatusText();
}
@Override
public void removeUpdate(DocumentEvent e) {
clearStatusText();
}
};
textField.getDocument().addDocumentListener(docListener);
setFocusComponent(textField);
}
private void buildMainPanel() {
JPanel panel = new JPanel(new PairLayout(5, 5, 120));
textField = new MyHintTextField(HINT_TEXT);
textField.setText(initialValue);
textField.addKeyListener(keyListener);
textField.setName("number.range.input.dialog.text.field");
panel.add(new GLabel(inputLabel, SwingConstants.RIGHT));
panel.add(textField);
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
this.addWorkPanel(panel);
}
/**
* <code>show</code> displays the dialog, gets the user input
*
* @return false if the user cancelled the operation
*/
public boolean show() {
DockingWindowManager.showDialog(this);
return !wasCancelled;
}
@Override
protected void okCallback() {
wasCancelled = false;
if (!parseRanges()) {
return;
}
close();
}
JTextField getTextField() {
return textField;
}
private boolean parseRanges() {
// format: 1
// 1,4
// 1-4
// 1-4,8
// -8, 0xc, 0x10:0x20
// -0x20:-0x10
String value = textField.getText();
String[] parts = value.split(",");
for (String rangeText : parts) {
if (!addRange(rangeText)) {
return false;
}
}
return true;
}
private boolean addRange(String rangeText) {
String trimmed = rangeText.trim();
if (!trimmed.contains(RANGE_DELIMITER)) {
try {
long parsedLong = NumericUtilities.parseLong(trimmed);
int intValue = (int) parsedLong;
rangeList.addRange(intValue, intValue);
}
catch (NumberFormatException e) {
setStatusText("Unable to parse as a number: '" + trimmed + "'");
return false;
}
return true;
}
// this must be a range
String[] startAndEnd = trimmed.split(RANGE_DELIMITER);
try {
long parsedLong = NumericUtilities.parseLong(startAndEnd[0]);
int startInt = (int) parsedLong;
parsedLong = NumericUtilities.parseLong(startAndEnd[1]);
int endInt = (int) parsedLong;
rangeList.addRange(startInt, endInt);
}
catch (NumberFormatException e) {
setStatusText("Unable to parse as a number: '" + trimmed + "'");
return false;
}
return true;
}
@Override
protected void cancelCallback() {
wasCancelled = true;
rangeList.clear();
close();
}
/**
* Returns if this dialog is cancelled
* @return true if cancelled
*/
public boolean wasCancelled() {
return wasCancelled;
}
/**
* Return the value of the first (and maybe only) text field
* @return the text field value
*/
public SortedRangeList getValue() {
return rangeList;
}
/**
* Sets the text of the primary text field
* @param text the text
*/
public void setValue(String text) {
textField.setText(text);
}
private class MyHintTextField extends HintTextField {
MyHintTextField(String hintText) {
super(hintText);
setColumns(20);
}
@Override
protected Document createDefaultModel() {
return new MyDocument(this);
}
private class MyDocument extends PlainDocument {
private JTextField documentTf;
private MyDocument(JTextField textField) {
super();
this.documentTf = textField;
}
@Override
public void insertString(int offs, String str, AttributeSet a)
throws BadLocationException {
if (str == null) {
return;
}
String text = documentTf.getText();
if (text.length() + str.length() > MAX_SIZE) {
int nTooMany = text.length() + str.length() - MAX_SIZE;
int len = str.length() - nTooMany;
str = str.substring(0, len);
}
super.insertString(offs, str, a);
}
}
}
}

View file

@ -29,10 +29,10 @@ public class ClassSearchTask extends Task {
}
@Override
public void run(final TaskMonitor taskMonitor) {
public void run(final TaskMonitor monitor) {
try {
ClassSearcher.search(true, taskMonitor);
ClassSearcher.search(true, monitor);
}
catch (CancelledException e) {
// user cancelled

View file

@ -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.
@ -30,7 +29,7 @@ public class SortedRangeList implements Iterable<Range> {
* Creates a new empty sorted range list.
*/
public SortedRangeList() {
set = new TreeSet<Range>();
set = new TreeSet<>();
}
/**
@ -39,7 +38,7 @@ public class SortedRangeList implements Iterable<Range> {
* @param list the sorted range list to make an equivalent copy of.
*/
public SortedRangeList(SortedRangeList list) {
set = new TreeSet<Range>();
set = new TreeSet<>();
Iterator<Range> it = list.set.iterator();
while (it.hasNext()) {
Range r = it.next();
@ -98,7 +97,7 @@ public class SortedRangeList implements Iterable<Range> {
return set.iterator();
}
Iterator<Range> it = set.iterator();
LinkedList<Range> ll = new LinkedList<Range>();
LinkedList<Range> ll = new LinkedList<>();
while (it.hasNext()) {
ll.addFirst(it.next());
}
@ -362,4 +361,8 @@ public class SortedRangeList implements Iterable<Range> {
public Iterator<Range> iterator() {
return getRanges(true);
}
public void clear() {
set.clear();
}
}

View file

@ -41,7 +41,8 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager {
private ChangeListener classSearcherListener = e -> refresh();
/**
* Returns shared instance of built-in data-type manager.
* Returns shared instance of built-in data type manager.
* @return the manager
*/
public static synchronized BuiltInDataTypeManager getDataTypeManager() {
if (manager == null) {
@ -58,9 +59,6 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager {
return manager;
}
/**
* Constructor
*/
private BuiltInDataTypeManager() {
super(BUILT_IN_DATA_TYPES_NAME);
initialize();
@ -104,8 +102,6 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager {
populateBuiltInTypes();
}
/////////////////////////////////////
private void initialize() {
try {
populateBuiltInTypes();
@ -123,7 +119,8 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager {
protected void populateBuiltInTypes() {
int id = super.startTransaction("Populate");
try {
ArrayList<DataType> list = new ArrayList<>();
List<DataType> list = new ArrayList<>();
ClassFilter filter = new BuiltInDataTypeClassExclusionFilter();
List<BuiltInDataType> datatypes =
ClassSearcher.getInstances(BuiltInDataType.class, filter);
@ -141,7 +138,7 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager {
datatype.getName() + "'");
}
else if (list.size() != 1) {
throw new AssertException("Should be no duplicate named nuilt-in types");
throw new AssertException("Should be no duplicate named built-in types");
}
}
}
@ -156,7 +153,8 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager {
return DataTypeManager.BUILT_IN_ARCHIVE_UNIVERSAL_ID;
}
throw new IllegalArgumentException(
"Only Built-In data types can be resolved by the BuiltInTypes manager.");
"Only Built-in data types can be resolved by the " + getClass().getSimpleName() +
" manager.");
}
@Override
@ -197,7 +195,6 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager {
@Override
public void close() {
// do nothing - cannot close a built-in data type manager
// close performed automatically during shutdown
// cannot close a built-in data type manager; close performed automatically during shutdown
}
}

View file

@ -15,14 +15,18 @@
*/
package ghidra.util.datastruct;
import java.util.Iterator;
import java.util.stream.IntStream;
/**
* A class for holding a minimum and maximum signed int values that define a range.
*/
public class Range implements Comparable<Range> {
public class Range implements Comparable<Range>, Iterable<Integer> {
/** The range's minimum extent. */
public int min;
/** The range's maximum extent (inclusive). */
public int max;
/**
* Creates a range whose extent is from min to max.
* @param min the minimum extent.
@ -31,8 +35,8 @@ public class Range implements Comparable<Range> {
*/
public Range(int min, int max) {
if (max < min) {
throw new IllegalArgumentException("Range max (" + max
+ ") cannot be less than min (" + min + ").");
throw new IllegalArgumentException(
"Range max (" + max + ") cannot be less than min (" + min + ").");
}
this.min = min;
this.max = max;
@ -50,11 +54,11 @@ public class Range implements Comparable<Range> {
}
@Override
public boolean equals(Object obj) {
public boolean equals(Object obj) {
if (obj.getClass() != Range.class) {
return false;
}
Range other = (Range)obj;
Range other = (Range) obj;
return other.min == min && other.max == max;
}
@ -64,20 +68,29 @@ public class Range implements Comparable<Range> {
}
@Override
public String toString() {
return "("+min+","+max+")";
public String toString() {
return "(" + min + "," + max + ")";
}
/**
* Returns true if the value is within the ranges extent.
* @param value the value to check.
* @return true if the value is within the ranges extent.
*/
public boolean contains(int value) {
return value >= min && value <= max;
}
/**
* Returns the range's size.
* @return the size
*/
public long size() {
return (long)max - (long)min + 1;
return (long) max - (long) min + 1;
}
@Override
public Iterator<Integer> iterator() {
return IntStream.range(min, max + 1).iterator();
}
}