GP-4270: Minor fixes

This commit is contained in:
dragonmacher 2024-01-26 16:18:05 -05:00 committed by Ryan Kurtz
parent b9f914d57c
commit 1cf7803d88
22 changed files with 295 additions and 183 deletions

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.
@ -16,11 +15,11 @@
*/
package ghidra.app.decompiler;
import java.io.*;
import generic.concurrent.*;
import ghidra.util.task.TaskMonitor;
import java.io.*;
public class DecompilerDisposer {
private static String THREAD_POOL_NAME = "Decompiler Disposer";
private static ConcurrentQ<AbstractDisposable, AbstractDisposable> queue;
@ -38,7 +37,7 @@ public class DecompilerDisposer {
/**
* Disposes the given Process and related streams from a background thread. This is necessary
* due to a low-probability deadlock that occurs in the JVM.
*
*
* @param process The process to destroy.
* @param ouputStream The output stream to close
* @param inputStream The input stream to close
@ -54,23 +53,24 @@ public class DecompilerDisposer {
* <p>
* Note:<br>
* A class to handle the rare case where the {@link DecompInterface}'s
* synchronized methods are blocking
* synchronized methods are blocking
* while a decompile operation has died and maintained the lock. In that scenario, calling
* dispose on this class will eventually try to enter a synchronized method that will
* dispose on this class will eventually try to enter a synchronized method that will
* remain blocked forever.
* <p>
* I examined the uses of dispose() on the {@link DecompInterface} and
* I examined the uses of dispose() on the {@link DecompInterface} and
* determined that calling dispose() is a
* final operation, which means that you don't have to wait. Further, after calling
* dispose() on this class, you should no longer use it.
* @param decompiler the decompiler
*/
public static void dispose(DecompInterface decompiler) {
DecompInterfaceDisposable disposable = new DecompInterfaceDisposable(decompiler);
queue.add(disposable);
}
private static class DisposeCallback implements
QCallback<AbstractDisposable, AbstractDisposable> {
private static class DisposeCallback
implements QCallback<AbstractDisposable, AbstractDisposable> {
@Override
public AbstractDisposable process(AbstractDisposable disposable, TaskMonitor monitor) {
disposable.dispose();
@ -87,7 +87,8 @@ public class DecompilerDisposer {
private OutputStream ouputStream;
private InputStream inputStream;
RuntimeProcessDisposable(Process process, OutputStream ouputStream, InputStream inputStream) {
RuntimeProcessDisposable(Process process, OutputStream ouputStream,
InputStream inputStream) {
this.process = process;
this.ouputStream = ouputStream;
this.inputStream = inputStream;

View file

@ -20,8 +20,10 @@ import java.awt.Point;
import java.awt.event.MouseEvent;
import java.util.Objects;
import generic.json.Json;
/**
* A class that holds information used to show a popup menu
* A class that holds information used to show a popup menu
*/
public class PopupMenuContext {
@ -58,4 +60,9 @@ public class PopupMenuContext {
}
return component;
}
@Override
public String toString() {
return Json.toString(this);
}
}

View file

@ -74,10 +74,10 @@ public class DefaultDropDownSelectionDataModel<T> implements DropDownTextFieldDa
@Override
public int getIndexOfFirstMatchingEntry(List<T> list, String text) {
// The data are sorted such that lower-case is before upper-case and smaller length
// matches come before longer matches. If we ever find a case-sensitive exact match,
// use that. Otherwise, keep looking for a case-insensitive exact match. The
// case-insensitive match is preferred over a non-matching item. Once we get to a
// The data are sorted such that lower-case is before upper-case and smaller length
// matches come before longer matches. If we ever find a case-sensitive exact match,
// use that. Otherwise, keep looking for a case-insensitive exact match. The
// case-insensitive match is preferred over a non-matching item. Once we get to a
// non-matching item, we can quit.
int lastPreferredMatchIndex = -1;
for (int i = 0; i < list.size(); i++) {
@ -118,7 +118,7 @@ public class DefaultDropDownSelectionDataModel<T> implements DropDownTextFieldDa
//==================================================================================================
// Inner Classes
//==================================================================================================
//==================================================================================================
private class ObjectStringComparator implements Comparator<Object> {
Comparator<String> stringComparator = new CaseInsensitiveDuplicateStringComparator();

View file

@ -33,7 +33,6 @@ public class ListSelectionDialog<T> extends DialogComponentProvider {
private DropDownSelectionTextField<T> field;
protected boolean cancelled;
private RowObjectTableModel<T> userTableModel;
private DataToStringConverter<T> searchConverter;
private DataToStringConverter<T> descriptionConverter;
private List<T> data;
@ -61,8 +60,12 @@ public class ListSelectionDialog<T> extends DialogComponentProvider {
this.data = data;
this.searchConverter = searchConverter;
this.descriptionConverter = descriptionConverter;
// Use a separate list for the drop down widget, since it needs to sort its data and we do
// not want to change the client data sort.
List<T> dropDownData = new ArrayList<>(data);
DefaultDropDownSelectionDataModel<T> model = new DefaultDropDownSelectionDataModel<>(
new ArrayList<>(data), searchConverter, descriptionConverter) {
dropDownData, searchConverter, descriptionConverter) {
// overridden to return all data for an empty search; this lets the down-arrow
// show the full list
@ -151,14 +154,18 @@ public class ListSelectionDialog<T> extends DialogComponentProvider {
}
private RowObjectTableModel<T> getTableModel() {
if (userTableModel != null) {
return userTableModel;
}
return new DefaultTableModel();
return new DefaultTableModel(data);
}
private class DefaultTableModel extends AbstractGTableModel<T> {
private List<T> modelData;
DefaultTableModel(List<T> modelData) {
// copy the data so that a call to dispose() will not clear the data in the outer class
this.modelData = new ArrayList<>(modelData);
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex == 0) {
@ -184,7 +191,7 @@ public class ListSelectionDialog<T> extends DialogComponentProvider {
@Override
public List<T> getModelData() {
return data;
return modelData;
}
@Override

View file

@ -29,8 +29,6 @@ import utilities.util.reflection.ReflectionUtilities;
* determines the appropriate cell object for use by the table column this field represents. It can
* then return the appropriate object to display in the table cell for the indicated row object.
*
* Implementations of this interface must provide a public default constructor.
*
* @param <ROW_TYPE> The row object class supported by this column
* @param <COLUMN_TYPE> The column object class supported by this column
* @param <DATA_SOURCE> The object class type that will be passed to see
@ -95,8 +93,8 @@ public abstract class AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOU
public Class<COLUMN_TYPE> getColumnClass() {
@SuppressWarnings("rawtypes")
Class<? extends AbstractDynamicTableColumn> implementationClass = getClass();
List<Class<?>> typeArguments = ReflectionUtilities.getTypeArguments(
AbstractDynamicTableColumn.class, implementationClass);
List<Class<?>> typeArguments = ReflectionUtilities
.getTypeArguments(AbstractDynamicTableColumn.class, implementationClass);
return (Class<COLUMN_TYPE>) typeArguments.get(1);
}
@ -106,8 +104,8 @@ public abstract class AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOU
public Class<ROW_TYPE> getSupportedRowType() {
@SuppressWarnings("rawtypes")
Class<? extends AbstractDynamicTableColumn> implementationClass = getClass();
List<Class<?>> typeArguments = ReflectionUtilities.getTypeArguments(
AbstractDynamicTableColumn.class, implementationClass);
List<Class<?>> typeArguments = ReflectionUtilities
.getTypeArguments(AbstractDynamicTableColumn.class, implementationClass);
return (Class<ROW_TYPE>) typeArguments.get(0);
}
@ -193,7 +191,7 @@ public abstract class AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOU
return getIdentifier().hashCode();
}
// Note: this method is here because the default 'identifier' must be lazy loaded, as
// Note: this method is here because the default 'identifier' must be lazy loaded, as
// at construction time not all the variables needed are available.
private String getIdentifier() {
/*
@ -202,7 +200,7 @@ public abstract class AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, DATA_SOU
-The case where 2 different column classes share the same column header value
-The case where a single column class is used repeatedly, with a different
column header value each time
Thus, to be unique, we need to combine both the class name and the column header
value. The only time this may be an issue is if the column header value changes
dynamically--not sure if this actually happens anywhere in our system. If it did,

View file

@ -4,9 +4,9 @@
* 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.
@ -20,14 +20,18 @@ import ghidra.framework.plugintool.ServiceProvider;
/**
* This class is meant to be used by DynamicTableColumn implementations that do not care about
* 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
* call a version of the method that does not have the DATA_SOURCE parameter.
* <p>
* Subclasses are not discoverable. To create discoverable columns for the framework, you must
* extends {@link DynamicTableColumnExtensionPoint}.
*
* @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> {
public abstract class AbstractDynamicTableColumnStub<ROW_TYPE, COLUMN_TYPE>
extends AbstractDynamicTableColumn<ROW_TYPE, COLUMN_TYPE, Object> {
@Override
public COLUMN_TYPE getValue(ROW_TYPE rowObject, Settings settings, Object data,

View file

@ -156,7 +156,7 @@ public class AnyObjectTableModel<T> extends GDynamicColumnTableModel<T, Object>
}
private class MethodColumn extends AbstractDynamicTableColumn<T, Object, Object> {
private String name;
private String methodName;
private Method method;
private Class<?> returnType;
@ -166,7 +166,7 @@ public class AnyObjectTableModel<T> extends GDynamicColumnTableModel<T, Object>
init(m);
}
catch (NoSuchMethodException | SecurityException e) {
name = "No method: " + methodName;
this.methodName = "No method: " + methodName;
}
}
@ -181,12 +181,12 @@ public class AnyObjectTableModel<T> extends GDynamicColumnTableModel<T, Object>
private void init(Method m) {
this.method = m;
name = method.getName();
if (name.startsWith("get")) {
name = name.substring(3);
methodName = method.getName();
if (methodName.startsWith("get")) {
methodName = methodName.substring(3);
}
name = fromCamelCase(name);
methodName = fromCamelCase(methodName);
returnType = method.getReturnType();
}
@ -198,15 +198,15 @@ public class AnyObjectTableModel<T> extends GDynamicColumnTableModel<T, Object>
@Override
public String getColumnName() {
return name;
return methodName;
}
@Override
public Object getValue(T rowObject, Settings settings, Object dataSource,
ServiceProvider sp) throws IllegalArgumentException {
if (method == null) {
Msg.error(this,
"No method '" + name + "' on class" + rowObject.getClass().getSimpleName());
Msg.error(this, "No method '" + methodName + "' on class " +
rowObject.getClass().getSimpleName());
return null;
}
try {

View file

@ -76,7 +76,6 @@ public class GFilterTable<ROW_OBJECT> extends JPanel {
}
private void buildThreadedTable() {
@SuppressWarnings("unchecked")
GThreadedTablePanel<ROW_OBJECT> tablePanel =
createThreadedTablePanel((ThreadedTableModel<ROW_OBJECT, ?>) model);
table = tablePanel.getTable();

View file

@ -168,6 +168,20 @@ public class GTableWidget<T> extends JPanel {
return table.getSelectedRowCount();
}
/**
* Sets the selection mode of this table.
*
* @param mode the mode
* @see ListSelectionModel#setSelectionMode(int)
*/
public void setSelectionMode(int mode) {
table.getSelectionModel().setSelectionMode(mode);
}
public int getSelectionMode() {
return table.getSelectionModel().getSelectionMode();
}
public void addSelectionListener(ObjectSelectedListener<T> l) {
gFilterTable.addSelectionListener(l);
}

View file

@ -17,12 +17,13 @@ package docking.widgets.table;
import java.util.*;
import ghidra.util.Msg;
public class TableColumnDescriptor<ROW_TYPE> {
private List<TableColumnInfo> columns = new ArrayList<>();
public List<DynamicTableColumn<ROW_TYPE, ?, ?>> getAllColumns() {
List<DynamicTableColumn<ROW_TYPE, ?, ?>> list =
new ArrayList<>();
List<DynamicTableColumn<ROW_TYPE, ?, ?>> list = new ArrayList<>();
for (TableColumnInfo info : columns) {
list.add(info.column);
}
@ -30,8 +31,7 @@ public class TableColumnDescriptor<ROW_TYPE> {
}
public List<DynamicTableColumn<ROW_TYPE, ?, ?>> getDefaultVisibleColumns() {
List<DynamicTableColumn<ROW_TYPE, ?, ?>> list =
new ArrayList<>();
List<DynamicTableColumn<ROW_TYPE, ?, ?>> list = new ArrayList<>();
for (TableColumnInfo info : columns) {
if (info.isVisible) {
list.add(info.column);
@ -60,20 +60,31 @@ public class TableColumnDescriptor<ROW_TYPE> {
return editor.createTableSortState();
}
private int remove(DynamicTableColumn<ROW_TYPE, ?, ?> column) {
for (int i = 0; i < columns.size(); i++) {
TableColumnDescriptor<ROW_TYPE>.TableColumnInfo info = columns.get(i);
if (info.column == column) {
columns.remove(i);
return i;
}
public void setVisible(String columnName, boolean visible) {
TableColumnInfo info = getColumn(columnName);
if (info == null) {
Msg.debug(this,
"Unable to change visibility state of column '%s'".formatted(columnName));
return;
}
if (visible) {
info.isVisible = true;
}
else {
// remove and add a new info to clear any sort state info for a hidden column
int index = columns.indexOf(info);
columns.set(index, new TableColumnInfo(info.column));
}
return -1;
}
public void setHidden(DynamicTableColumn<ROW_TYPE, ?, ?> column) {
int index = remove(column);
columns.add(index, new TableColumnInfo(column));
private TableColumnInfo getColumn(String name) {
for (TableColumnInfo info : columns) {
String columnName = info.column.getColumnName();
if (columnName.equals(name)) {
return info;
}
}
return null;
}
public void addHiddenColumn(DynamicTableColumn<ROW_TYPE, ?, ?> column) {
@ -105,8 +116,8 @@ public class TableColumnDescriptor<ROW_TYPE> {
this.column = column;
}
TableColumnInfo(DynamicTableColumn<ROW_TYPE, ?, ?> column, boolean isVisible,
int sortIndex, boolean ascending) {
TableColumnInfo(DynamicTableColumn<ROW_TYPE, ?, ?> column, boolean isVisible, int sortIndex,
boolean ascending) {
this.column = column;
this.isVisible = isVisible;
this.sortIndex = sortIndex;

View file

@ -35,9 +35,11 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
/**
* Used to signal that the updateManager has finished loading the final contents gathered
* by this job.
* by this job. By default, the value is 0, which means there is nothing to wait for. If we
* flush, this will be set to 1.
*/
private final CountDownLatch completedCallbackLatch = new CountDownLatch(1);
private CountDownLatch completedCallbackLatch = new CountDownLatch(0);
private volatile boolean isCancelled = false;
private volatile IncrementalUpdatingAccumulator incrementalAccumulator;
@ -74,45 +76,49 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
}
boolean interrupted = Thread.currentThread().isInterrupted();
notifyCompleted(monitor.isCancelled() || interrupted);
notifyCompleted(hasBeenCancelled(monitor) || interrupted);
// all data should have been posted at this point; clean up any data left in the accumulator
incrementalAccumulator.clear();
}
private void doExecute(TaskMonitor monitor) {
try {
threadedModel.doLoad(incrementalAccumulator, monitor);
if (!monitor.isCancelled()) { // in case the model didn't call checkCancelled()
flush(incrementalAccumulator);
}
flush(incrementalAccumulator, monitor);
}
catch (CancelledException e) {
// handled by the caller of this method
isCancelled = true;
}
if (monitor.isCancelled()) {
return; // must leave now or we will block in the call below
}
waitForThreadedTableUpdateManager();
}
private void waitForThreadedTableUpdateManager() {
try {
completedCallbackLatch.await();
}
catch (InterruptedException e) {
// This implies the user has cancelled the job by starting a new one or that we have
// been disposed. Whatever the cause, we want to let the control flow continue as
// normal.
Thread.currentThread().interrupt(); // preserve the interrupt status
}
/**
* This method tracks cancelled from the given monitor and from any cancelled exceptions that
* happen during loading. When loading, the client may trigger a cancelled exception even
* though the monitor has not been cancelled.
* @param monitor the task monitor
* @return true if cancelled
*/
private boolean hasBeenCancelled(TaskMonitor monitor) {
return isCancelled || monitor.isCancelled();
}
private void flush(IncrementalUpdatingAccumulator accumulator) {
private void flush(IncrementalUpdatingAccumulator accumulator, TaskMonitor monitor) {
//
// Acquire the update manager lock so that it doesn't send out any events while we are
// giving it the data we just finished loading.
//
synchronized (updateManager.getSynchronizingLock()) {
if (hasBeenCancelled(monitor)) {
// Check for cancelled inside of this lock. This guarantees that no events will be
// sent out before we can add our listener
return;
}
// push the data to the update manager...
accumulator.flushData();
@ -135,8 +141,23 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
// -A block on jobDone() can now complete as we release the lock
// -jobDone() will notify listeners in an invokeLater(), which puts it behind ours
//
completedCallbackLatch = new CountDownLatch(1);
Swing.runLater(() -> updateManager.addThreadedTableListener(IncrementalLoadJob.this));
}
waitForThreadedTableUpdateManagerToFinish();
}
private void waitForThreadedTableUpdateManagerToFinish() {
try {
completedCallbackLatch.await();
}
catch (InterruptedException e) {
// This implies the user has cancelled the job by starting a new one or that we have
// been disposed. Whatever the cause, we want to let the control flow continue as
// normal.
Thread.currentThread().interrupt(); // preserve the interrupt status
}
}
private void notifyStarted(TaskMonitor monitor) {
@ -151,14 +172,19 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
}
updateManager.removeThreadedTableListener(this);
}
@Override
public void cancel() {
updateManager.getTaskMonitor().cancel();
// monitor.cancel(); TODO: are we handling this ??
super.cancel();
isCancelled = true;
incrementalAccumulator.cancel();
// Note: cannot do this here, since the cancel() call may happen asynchronously and after
// a call to reload() on the table model. Assume that the model itself has already
// cancelled the update manager when the worker queue was cancelled. See
// ThreadedTableModel.reload().
// updateManager.cancelAllJobs();
}
@Override
@ -188,14 +214,15 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
* is being provided to the accumulator.
*/
private class IncrementalUpdatingAccumulator extends SynchronizedListAccumulator<ROW_OBJECT> {
private volatile boolean cancelledOrDone;
private volatile boolean isDone;
private Runnable runnable = () -> {
if (cancelledOrDone) {
if (isCancelledOrDone()) {
// this handles the case where a cancel request came in off the Swing
// thread whilst we were already posted
return;
}
try {
updateManager.reloadSpecificData(asList());
}
@ -203,7 +230,7 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
// note: check for cancelled again, as it may have been called after the initial
// check above if the cancel call was requested off the Swing thread.
if (!cancelledOrDone) {
if (!isCancelledOrDone()) {
Msg.error(this, "Exception incrementally loading table data", e);
}
}
@ -219,8 +246,11 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
swingUpdateManager.update();
}
private boolean isCancelledOrDone() {
return isCancelled || isDone;
}
void cancel() {
cancelledOrDone = true;
swingUpdateManager.dispose();
}
@ -233,7 +263,7 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
}
void flushData() {
cancelledOrDone = true;
isDone = true;
swingUpdateManager.dispose();
updateManager.reloadSpecificData(asList());
}

View file

@ -4,9 +4,9 @@
* 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.

View file

@ -145,6 +145,10 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
updateManager.addThreadedTableListener(new NonIncrementalUpdateManagerListener());
}
startInitialLoad();
}
protected void startInitialLoad() {
// We are expecting to be in the swing thread. We want the reload to happen after our
// constructor is fully completed since the reload will cause our initialize method to
// be called in another thread, thereby creating a possible race condition.

View file

@ -36,27 +36,30 @@ public class TestDataKeyModel extends ThreadedTableModelStub<Long> {
public final static int STRING_COL = 6;
private Byte[] bytes = new Byte[] { Byte.valueOf((byte) 0x09), Byte.valueOf((byte) 0x03),
Byte.valueOf((byte) 0x0c), Byte.valueOf((byte) 0x55), Byte.valueOf((byte) 0x00), Byte.valueOf((byte) 0xdf),
Byte.valueOf((byte) 0xff), Byte.valueOf((byte) 0x03), Byte.valueOf((byte) 0x16), Byte.valueOf((byte) 0x02),
Byte.valueOf((byte) 0x03), Byte.valueOf((byte) 0x04), };
Byte.valueOf((byte) 0x0c), Byte.valueOf((byte) 0x55), Byte.valueOf((byte) 0x00),
Byte.valueOf((byte) 0xdf), Byte.valueOf((byte) 0xff), Byte.valueOf((byte) 0x03),
Byte.valueOf((byte) 0x16), Byte.valueOf((byte) 0x02), Byte.valueOf((byte) 0x03),
Byte.valueOf((byte) 0x04), };
private Short[] shorts = new Short[] { Short.valueOf((short) 0x0841), Short.valueOf((short) 0xb0f7),
Short.valueOf((short) 0xf130), Short.valueOf((short) 0x84e3), Short.valueOf((short) 0x2976),
Short.valueOf((short) 0x17d9), Short.valueOf((short) 0xf146), Short.valueOf((short) 0xc4a5),
Short.valueOf((short) 0x88f1), Short.valueOf((short) 0x966d), Short.valueOf((short) 0x966e),
Short.valueOf((short) 0x966f), };
private Short[] shorts = new Short[] { Short.valueOf((short) 0x0841),
Short.valueOf((short) 0xb0f7), Short.valueOf((short) 0xf130), Short.valueOf((short) 0x84e3),
Short.valueOf((short) 0x2976), Short.valueOf((short) 0x17d9), Short.valueOf((short) 0xf146),
Short.valueOf((short) 0xc4a5), Short.valueOf((short) 0x88f1), Short.valueOf((short) 0x966d),
Short.valueOf((short) 0x966e), Short.valueOf((short) 0x966f), };
private Integer[] ints =
new Integer[] { Integer.valueOf(0x039D492B), Integer.valueOf(0x0A161497), Integer.valueOf(0x06AA1497),
Integer.valueOf(0x0229EE9E), Integer.valueOf(0xFB7428E1), Integer.valueOf(0xD2B4ED2F),
Integer.valueOf(0x0C1F67DE), Integer.valueOf(0x0E61C987), Integer.valueOf(0x0133751F),
Integer.valueOf(0x07B39541), Integer.valueOf(0x07B39542), Integer.valueOf(0x07B39542), };
private Integer[] ints = new Integer[] { Integer.valueOf(0x039D492B),
Integer.valueOf(0x0A161497), Integer.valueOf(0x06AA1497), Integer.valueOf(0x0229EE9E),
Integer.valueOf(0xFB7428E1), Integer.valueOf(0xD2B4ED2F), Integer.valueOf(0x0C1F67DE),
Integer.valueOf(0x0E61C987), Integer.valueOf(0x0133751F), Integer.valueOf(0x07B39541),
Integer.valueOf(0x07B39542), Integer.valueOf(0x07B39542), };
private Long[] longs = new Long[] { Long.valueOf(0x0000000DFAA00C4FL),
Long.valueOf(0x00000001FD7CA6A6L), Long.valueOf(0xFFFFFFF4D0EB4AB8L), Long.valueOf(0x0000000445246143L),
Long.valueOf(0xFFFFFFF5696F1780L), Long.valueOf(0x0000000685526E5DL), Long.valueOf(0x00000009A1FD98EEL),
Long.valueOf(0x00000004AD2B1869L), Long.valueOf(0x00000002928E64C8L), Long.valueOf(0x000000071CE1DDB2L),
Long.valueOf(0x000000071CE1DDB3L), Long.valueOf(0x000000071CE1DDB4L), };
private Long[] longs =
new Long[] { Long.valueOf(0x0000000DFAA00C4FL), Long.valueOf(0x00000001FD7CA6A6L),
Long.valueOf(0xFFFFFFF4D0EB4AB8L), Long.valueOf(0x0000000445246143L),
Long.valueOf(0xFFFFFFF5696F1780L), Long.valueOf(0x0000000685526E5DL),
Long.valueOf(0x00000009A1FD98EEL), Long.valueOf(0x00000004AD2B1869L),
Long.valueOf(0x00000002928E64C8L), Long.valueOf(0x000000071CE1DDB2L),
Long.valueOf(0x000000071CE1DDB3L), Long.valueOf(0x000000071CE1DDB4L), };
private Float[] floats =
new Float[] { Float.valueOf((float) 0.143111240), Float.valueOf((float) 0.084097680),
@ -77,7 +80,7 @@ public class TestDataKeyModel extends ThreadedTableModelStub<Long> {
protected String[] strings = new String[] { "one", "two", "THREE", "Four", "FiVe", "sIx",
"SeVEn", "EighT", "NINE", "ten", "ten", "ten" };
private long timeBetweenAddingDataItemsInMillis = 1;
private volatile long timeBetweenAddingDataItemsInMillis = 1;
private volatile IncrementalLoadJob<Long> loadJob = null;

View file

@ -48,4 +48,9 @@ public class ConcurrentListenerSet<T> implements Iterable<T> {
public List<T> asList() {
return new ArrayList<>(storage.keySet());
}
@Override
public String toString() {
return asList().toString();
}
}

View file

@ -41,14 +41,14 @@ import ghidra.util.task.TaskMonitor;
* // do work here...
* }
* };
*
*
* ConcurrentQBuilder<ITEM, RESULT> builder = new ConcurrentQBuilder<ITEM, RESULT>();
* builder.setThreadPoolName("Thread Pool Name");
* concurrentQ = builder.getQueue(callback);
* ...
* ...
* concurrentQ.add(item); // where item is one of the instances of ITEM
*
*
* }</pre>
* <hr>
* <p>
@ -59,14 +59,14 @@ import ghidra.util.task.TaskMonitor;
* // do work here...
* }
* };
*
*
* {@literal QItemListener<ITEM, RESULT> itemListener = new QItemListener<ITEM, RESULT>()} {
* {@literal public void itemProcessed(QResult<ITEM, RESULT> result)} {
* RESULT result = result.getResult();
* <span style="color:blue"><b>// work on my result...</b></span>
* }
* };
*
*
* {@literal ConcurrentQBuilder<ITEM, RESULT> builder = new ConcurrentQBuilder<ITEM, RESULT>()};
* builder.setThreadPoolName("Thread Pool Name");
* <span style="color:blue"><b>builder.setListener(itemListener);</b></span>
@ -76,9 +76,9 @@ import ghidra.util.task.TaskMonitor;
* concurrentQ.add(item); // where item is one of the instances of ITEM
* concurrentQ.add(item);
* concurrentQ.add(item);
*
*
* </pre>
*
*
* <hr>
* <p>
* <u>Put Items and Handle Results When All Items Have Been Processed:</u>
@ -99,10 +99,10 @@ import ghidra.util.task.TaskMonitor;
* concurrentQ.add(item);
* concurrentQ.add(item);
* ...
*
*
* <span style="color:blue"><b>{@literal List<QResult<I, R>> results = concurrentQ.waitForResults();}</b></span>{@literal
* // process the results...
*
*
* }</pre>
* <hr>
* <p>
@ -120,7 +120,7 @@ import ghidra.util.task.TaskMonitor;
* // work on my result...
* }
* };
*
*
* {@literal ConcurrentQBuilder<ITEM, RESULT> builder = new ConcurrentQBuilder<ITEM, RESULT>()};
* builder.setThreadPoolName("Thread Pool Name");
* <span style="color:blue"><b>builder.setQueue(new LinkedBlockingQueue(100));</b></span>
@ -129,10 +129,10 @@ import ghidra.util.task.TaskMonitor;
* ...
* {@literal Iterator<ITEM> iterator = <get an iterator for 1000s of items somewhere>}
* <span style="color:blue"><b>{@code concurrentQ.offer(iterator); // this call will block when the queue fills up (100 items or more)}</b></span>
*
*
* </pre>
* <hr>
*
*
* @param <I> The type of the items to be processed.
* @param <R> The type of objects resulting from processing an item; if you don't care about the
* return value, then make this value whatever you want, like <code>Object</code> or the
@ -162,7 +162,7 @@ public class ConcurrentQ<I, R> {
/**
* Creates a ConcurrentQ that will process as many items as the given threadPool can handle
* at one time.
*
*
* @param name The name of the thread pool that will be created by this constructor.
* @param callback the QWorker object that will be used to process items concurrently.
*/
@ -174,7 +174,7 @@ public class ConcurrentQ<I, R> {
/**
* Creates a ConcurrentQ that will process at most maxInProgress items at a time, regardless of
* how many threads are available in the GThreadPool.
*
*
* @param callback the QWorker object that will be used to process items concurrently.
* @param queue the internal storage queue to use in this concurrent queue.
* @param threadPool the GThreadPool to used for providing the threads for concurrent processing.
@ -210,7 +210,7 @@ public class ConcurrentQ<I, R> {
/**
* Adds a progress listener for this queue. All the progress and messages reported by a
* QWorker will be routed to these listener.
*
*
* @param listener the listener for receiving progress and message notifications.
*/
public synchronized void addProgressListener(QProgressListener<I> listener) {
@ -239,7 +239,7 @@ public class ConcurrentQ<I, R> {
/**
* Sets the monitor to use with this queue.
*
*
* @param monitor the monitor to attache to this queue
* @param cancelClearsAllItems if true, cancelling the monitor will cancel all items currently
* being processed by a thread and clear the scheduled
@ -299,7 +299,7 @@ public class ConcurrentQ<I, R> {
* <p>
* To enable blocking on the queue when it is full, construct this <code>ConcurrentQ</code>
* with an instance of {@link BlockingQueue}.
*
*
* @param iterator An iterator from which items will be taken.
* @throws InterruptedException if this queue is interrupted while waiting to add more items
*/
@ -355,7 +355,7 @@ public class ConcurrentQ<I, R> {
* <P>
* You can still call this method to wait for items to be processed, even if you did not
* specify to collect results. In that case, the list returned will be empty.
*
*
* @return the list of QResult objects that have all the results of the completed jobs.
* @throws InterruptedException if this call was interrupted--Note: this interruption only
* happens if the calling thread cannot acquire the lock. If the thread is
@ -376,7 +376,7 @@ public class ConcurrentQ<I, R> {
/**
* Wait until at least one result is available and then return the first result.
*
*
* @return the first available result
* @throws InterruptedException if interrupted while waiting for a result
* @throws IllegalStateException if this queue has been set to not collect results
@ -416,7 +416,7 @@ public class ConcurrentQ<I, R> {
* all results, both with and without exceptions, which you can then process, including
* checking for exceptions. Note that to use {@link #waitForResults()} to examine exceptions,
* you must have created this queue with <code>collectResults</code> as true.
*
*
* @throws InterruptedException if interrupted while waiting for a result
* @throws Exception any exception encountered while processing an item (this will cancel all
* items in the queue).
@ -451,7 +451,7 @@ public class ConcurrentQ<I, R> {
* <P>
* You can still call this method to wait for items to be processed, even if you did not
* specify to collect results. In that case, the list returned will be empty.
*
*
* @param timeout the timeout
* @param unit the timeout unit
* @return the list of QResult objects that have all the results of the completed jobs.
@ -481,7 +481,7 @@ public class ConcurrentQ<I, R> {
* they check the isCancelled() state of their QMonitor, it will be true. Setting the
* interruptRunningTasks to true, will result in a thread interrupt to any currently running
* task which might be useful if the task perform waiting operations like I/O.
*
*
* @param interruptRunningTasks if true, an attempt will be made to interrupt any currently
* processing thread.
* @return a list of all items that have not yet been queued to the threadPool.
@ -541,6 +541,14 @@ public class ConcurrentQ<I, R> {
if (threadPool.isPrivate()) {
threadPool.shutdownNow();
}
lock.lock();
try {
resultList.clear();
}
finally {
lock.unlock();
}
}
public boolean waitUntilDone(long timeout, TimeUnit unit) throws InterruptedException {

View file

@ -24,11 +24,11 @@ import ghidra.util.task.TaskMonitor;
/**
* A helper class to build up the potentially complicated {@link ConcurrentQ}.
* <P>
* Note: you must supply either a {@link GThreadPool} instance or a thread pool name. Further,
* Note: you must supply either a {@link GThreadPool} instance or a thread pool name. Further,
* if you supply the name of a thread pool, then a private, non-shared pool will be used. If you
* wish to make use of a shared pool, then you need to create that thread pool yourself. See
* {@link GThreadPool#getSharedThreadPool(String)}.
*
*
* <P>
* Examples:
* <p>
@ -38,25 +38,25 @@ import ghidra.util.task.TaskMonitor;
* // do work here...
* }
* };
*
*
* ConcurrentQBuilder<I, R> builder = new ConcurrentQBuilder<I, R>();
* builder.setThreadPoolName("Thread Pool Name");
* builder.setQueue(new PriorityBlockingQueue());
* concurrentQ = builder.build(callback);
*
*
* // OR, you can chain the builder calls:
* ConcurrentQBuilder<I, R> builder = new ConcurrentQBuilder<I, R>();
* queue = builder.setThreadPoolName("Thread Pool Name").
* setQueue(new PriorityBlockingQueue()).
* setMaxInProgress(1).
* build(callback);
*
*
* }</pre>
* <p>
*
* Note: if you wish to take advantage of blocking when adding items to the {@link ConcurrentQ},
*
* Note: if you wish to take advantage of blocking when adding items to the {@link ConcurrentQ},
* see {@link #setQueue(Queue)}.
*
*
*
*
* @param <I> The type of the items to be processed.
@ -74,14 +74,14 @@ public class ConcurrentQBuilder<I, R> {
private boolean cancelClearsAllJobs = true;
/**
* Sets the queue to be used by the {@link ConcurrentQ}. If you would like advanced features,
* like a queue that blocks when too many items have been placed in it, then use an
* Sets the queue to be used by the {@link ConcurrentQ}. If you would like advanced features,
* like a queue that blocks when too many items have been placed in it, then use an
* advanced queue here, such as a {@link LinkedBlockingQueue}.
* <p>
* Note: if you wish to take advantage of blocking when adding items to the {@link ConcurrentQ},
* then be sure to call the appropriate method, such as
* Note: if you wish to take advantage of blocking when adding items to the {@link ConcurrentQ},
* then be sure to call the appropriate method, such as
* {@link ConcurrentQ#offer(java.util.Iterator)}.
*
*
* @param queue the queue to be used by the {@link ConcurrentQ}
* @return this builder
*/
@ -91,14 +91,14 @@ public class ConcurrentQBuilder<I, R> {
}
/**
* Specifies the maximum number of items that can be process at a time.
* If this is set to 0, then the concurrent queue will attempt to execute as many
* items at a time as there are threads in the given threadPool. Setting
* Specifies the maximum number of items that can be process at a time.
* If this is set to 0, then the concurrent queue will attempt to execute as many
* items at a time as there are threads in the given threadPool. Setting
* this parameter to 1 will have the effect of guaranteeing that
* all times are processed one at a time in the order they were submitted.
* Any other positive value will run that many items concurrently,
* Any other positive value will run that many items concurrently,
* up to the number of available threads.
*
*
* @param max the max number of items to execute at one time; defaults to 0
* @return this builder instance
*/
@ -109,9 +109,9 @@ public class ConcurrentQBuilder<I, R> {
/**
* Sets the name to be used when creating a <b>private thread pool</b>. If you wish to use
* a <i>shared thread pool</i>, then you need to create that thread pool youself and call
* a <i>shared thread pool</i>, then you need to create that thread pool yourself and call
* {@link #setThreadPool(GThreadPool)}.
*
*
* @param name the name of the thread pool.
* @return this builder instance
* @see GThreadPool#getSharedThreadPool(String)
@ -123,9 +123,9 @@ public class ConcurrentQBuilder<I, R> {
/**
* Use the given thread pool for processing the work items. If you do not care to configure
* the thread pool used and you do not wish to make use of shared thread pools, then you
* the thread pool used and you do not wish to make use of shared thread pools, then you
* can call {@link #setThreadPoolName(String)} instead of this method.
*
*
* @param threadPool the thread pool to use
* @return this builder instance
* @see GThreadPool#getSharedThreadPool(String)
@ -137,7 +137,8 @@ public class ConcurrentQBuilder<I, R> {
/**
* Specifies if the concurrent queue should collect the results as items are processed
* so they can be returned in a {@link ConcurrentQ#waitForResults()} call.
* so they can be returned in a {@link ConcurrentQ#waitForResults()} or
* {@link ConcurrentQ#waitForNextResult()} call.
* @param collectResults true signals to collect the generated results; defaults to false
* @return this builder instance
*/
@ -147,18 +148,18 @@ public class ConcurrentQBuilder<I, R> {
}
/**
* True signals that the jobs run by the client wish to report progress. The default value
* True signals that the jobs run by the client wish to report progress. The default value
* is false.
* <p>
* The default of false is good for clients that have a known amount of work to be processed.
* In this case, a total count of work jobs is maintained by the queue. As items are
* In this case, a total count of work jobs is maintained by the queue. As items are
* completed, the queue will update the monitor provided to it at construction time to reflect
* the number of jobs completed as work is done. On the other hand, some clients have
* known known number of jobs to complete, but simply add work to the queue as it arrives.
* In that case, the client should update its monitor for progress, as the queue cannot
* the number of jobs completed as work is done. On the other hand, some clients have
* known known number of jobs to complete, but simply add work to the queue as it arrives.
* In that case, the client should update its monitor for progress, as the queue cannot
* do so in a meaningful way.
*
* @param reportsProgress true signals that the client will update progress; false signals
*
* @param reportsProgress true signals that the client will update progress; false signals
* that the queue should do so
* @return this builder instance
*/
@ -178,12 +179,12 @@ public class ConcurrentQBuilder<I, R> {
}
/**
* Sets whether a cancel will clear all jobs (current and pending) or just the
* Sets whether a cancel will clear all jobs (current and pending) or just the
* current jobs being processed. The default value is {@code true}.
*
* @param clearAllJobs if true, cancelling the monitor will cancel all items currently being
* processed by a thread and clear the scheduled items that haven't yet run. If false,
* only the items currently being processed will be cancelled.
*
* @param clearAllJobs if true, cancelling the monitor will cancel all items currently being
* processed by a thread and clear the scheduled items that haven't yet run. If false,
* only the items currently being processed will be cancelled.
* @return this builder
* @see ConcurrentQ#setMonitor(TaskMonitor, boolean)
*/
@ -194,9 +195,8 @@ public class ConcurrentQBuilder<I, R> {
public ConcurrentQ<I, R> build(QCallback<I, R> callback) {
ConcurrentQ<I, R> concurrentQ =
new ConcurrentQ<>(callback, getQueue(), getThreadPool(), listener, collectResults,
maxInProgress, jobsReportProgress);
ConcurrentQ<I, R> concurrentQ = new ConcurrentQ<>(callback, getQueue(), getThreadPool(),
listener, collectResults, maxInProgress, jobsReportProgress);
if (monitor != null) {
concurrentQ.setMonitor(monitor, cancelClearsAllJobs);

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.
@ -59,6 +58,10 @@ public class SynchronizedListAccumulator<T> implements Accumulator<T> {
return list.size();
}
public void clear() {
list.clear();
}
@Override
public synchronized Iterator<T> iterator() {
return asList().iterator();

View file

@ -28,6 +28,8 @@ public abstract class Job {
/**
* The method that gets called by the Worker when this job is selected to be run
* by the Worker.
* @param monitor the monitor
* @throws CancelledException jobs may choose to throw a cancelled exception
*/
public abstract void run(TaskMonitor monitor) throws CancelledException;

View file

@ -155,6 +155,7 @@ public class GThemeDefaults {
public static final GColor PURPLE = getColor("purple");
public static final GColor RED = getColor("red");
public static final GColor SILVER = getColor("silver");
public static final GColor TEAL = getColor("teal");
public static final GColor WHITE = getColor("white");
public static final GColor YELLOW = getColor("yellow");

View file

@ -633,7 +633,7 @@ public abstract class PluginTool extends AbstractDockingTool {
}
winMgr.restoreWindowDataFromXml(root);
winMgr.setToolName(fullName);
updateTitle();
return hasErrors;
}

View file

@ -225,21 +225,37 @@ public abstract class StandAloneApplication implements GenericStandAloneApplicat
}
private Element getSavedToolElement() {
File savedToolFile = new File(Application.getUserSettingsDirectory(), SAVED_TOOL_FILE);
if (!savedToolFile.exists()) {
return null;
File userSettingsDir = Application.getUserSettingsDirectory();
File savedToolFile = new File(userSettingsDir, SAVED_TOOL_FILE);
if (savedToolFile.exists()) {
return loadToolXml(savedToolFile);
}
Msg.debug(this, "No saved tool found in " + userSettingsDir);
List<File> dirs = GenericRunInfo.getPreviousApplicationSettingsDirsByTime();
for (File dir : dirs) {
savedToolFile = new File(dir, SAVED_TOOL_FILE);
Msg.debug(this, "Checking for previous tool in " + dir);
if (savedToolFile.exists()) {
Msg.debug(this, "Using previous tool " + savedToolFile);
return loadToolXml(savedToolFile);
}
}
return null;
}
private Element loadToolXml(File file) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(savedToolFile.getAbsolutePath());
fileInputStream = new FileInputStream(file.getAbsolutePath());
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Element root = sax.build(fileInputStream).getRootElement();
return root;
}
catch (Exception e) {
Msg.showError(getClass(), null, "Error Reading Tool",
"Could not read tool: " + savedToolFile, e);
Msg.showError(getClass(), null, "Error Reading Tool", "Could not read tool: " + file,
e);
}
finally {
if (fileInputStream != null) {
@ -251,7 +267,6 @@ public abstract class StandAloneApplication implements GenericStandAloneApplicat
}
}
}
return null;
}