diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerDisposer.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerDisposer.java index b2afc261fe..f78340562d 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerDisposer.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompilerDisposer.java @@ -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 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 { *

* Note:
* 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. *

- * 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 { + private static class DisposeCallback + implements QCallback { @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; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java index 16c87cbd7f..6a9032660b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java @@ -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); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/DefaultDropDownSelectionDataModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/DefaultDropDownSelectionDataModel.java index 3fa2908d21..a61c9d6437 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/DefaultDropDownSelectionDataModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/DefaultDropDownSelectionDataModel.java @@ -74,10 +74,10 @@ public class DefaultDropDownSelectionDataModel implements DropDownTextFieldDa @Override public int getIndexOfFirstMatchingEntry(List 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 implements DropDownTextFieldDa //================================================================================================== // Inner Classes -//================================================================================================== +//================================================================================================== private class ObjectStringComparator implements Comparator { Comparator stringComparator = new CaseInsensitiveDuplicateStringComparator(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionDialog.java index efad5207bd..609ac5c9c9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/ListSelectionDialog.java @@ -33,7 +33,6 @@ public class ListSelectionDialog extends DialogComponentProvider { private DropDownSelectionTextField field; protected boolean cancelled; - private RowObjectTableModel userTableModel; private DataToStringConverter searchConverter; private DataToStringConverter descriptionConverter; private List data; @@ -61,8 +60,12 @@ public class ListSelectionDialog 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 dropDownData = new ArrayList<>(data); DefaultDropDownSelectionDataModel 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 extends DialogComponentProvider { } private RowObjectTableModel getTableModel() { - if (userTableModel != null) { - return userTableModel; - } - - return new DefaultTableModel(); + return new DefaultTableModel(data); } private class DefaultTableModel extends AbstractGTableModel { + + private List modelData; + + DefaultTableModel(List 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 extends DialogComponentProvider { @Override public List getModelData() { - return data; + return modelData; } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractDynamicTableColumn.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractDynamicTableColumn.java index b0b9683370..e3db148d4c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractDynamicTableColumn.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AbstractDynamicTableColumn.java @@ -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 The row object class supported by this column * @param The column object class supported by this column * @param The object class type that will be passed to see @@ -95,8 +93,8 @@ public abstract class AbstractDynamicTableColumn getColumnClass() { @SuppressWarnings("rawtypes") Class implementationClass = getClass(); - List> typeArguments = ReflectionUtilities.getTypeArguments( - AbstractDynamicTableColumn.class, implementationClass); + List> typeArguments = ReflectionUtilities + .getTypeArguments(AbstractDynamicTableColumn.class, implementationClass); return (Class) typeArguments.get(1); } @@ -106,8 +104,8 @@ public abstract class AbstractDynamicTableColumn getSupportedRowType() { @SuppressWarnings("rawtypes") Class implementationClass = getClass(); - List> typeArguments = ReflectionUtilities.getTypeArguments( - AbstractDynamicTableColumn.class, implementationClass); + List> typeArguments = ReflectionUtilities + .getTypeArguments(AbstractDynamicTableColumn.class, implementationClass); return (Class) typeArguments.get(0); } @@ -193,7 +191,7 @@ public abstract class AbstractDynamicTableColumn + * Subclasses are not discoverable. To create discoverable columns for the framework, you must + * extends {@link DynamicTableColumnExtensionPoint}. + * * @param the row type * @param the column type */ -public abstract class AbstractDynamicTableColumnStub extends - AbstractDynamicTableColumn { +public abstract class AbstractDynamicTableColumnStub + extends AbstractDynamicTableColumn { @Override public COLUMN_TYPE getValue(ROW_TYPE rowObject, Settings settings, Object data, diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AnyObjectTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AnyObjectTableModel.java index 57f6c31c96..cc0e3bee68 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AnyObjectTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/AnyObjectTableModel.java @@ -156,7 +156,7 @@ public class AnyObjectTableModel extends GDynamicColumnTableModel } private class MethodColumn extends AbstractDynamicTableColumn { - private String name; + private String methodName; private Method method; private Class returnType; @@ -166,7 +166,7 @@ public class AnyObjectTableModel extends GDynamicColumnTableModel init(m); } catch (NoSuchMethodException | SecurityException e) { - name = "No method: " + methodName; + this.methodName = "No method: " + methodName; } } @@ -181,12 +181,12 @@ public class AnyObjectTableModel extends GDynamicColumnTableModel 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 extends GDynamicColumnTableModel @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 { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.java index 6791b12215..1869960cc0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.java @@ -76,7 +76,6 @@ public class GFilterTable extends JPanel { } private void buildThreadedTable() { - @SuppressWarnings("unchecked") GThreadedTablePanel tablePanel = createThreadedTablePanel((ThreadedTableModel) model); table = tablePanel.getTable(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java index 733c1bc9ab..530f24b88f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java @@ -168,6 +168,20 @@ public class GTableWidget 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 l) { gFilterTable.addSelectionListener(l); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java index 794f6817f4..a4e1fb9537 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java @@ -17,12 +17,13 @@ package docking.widgets.table; import java.util.*; +import ghidra.util.Msg; + public class TableColumnDescriptor { private List columns = new ArrayList<>(); public List> getAllColumns() { - List> list = - new ArrayList<>(); + List> list = new ArrayList<>(); for (TableColumnInfo info : columns) { list.add(info.column); } @@ -30,8 +31,7 @@ public class TableColumnDescriptor { } public List> getDefaultVisibleColumns() { - List> list = - new ArrayList<>(); + List> list = new ArrayList<>(); for (TableColumnInfo info : columns) { if (info.isVisible) { list.add(info.column); @@ -60,20 +60,31 @@ public class TableColumnDescriptor { return editor.createTableSortState(); } - private int remove(DynamicTableColumn column) { - for (int i = 0; i < columns.size(); i++) { - TableColumnDescriptor.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 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 column) { @@ -105,8 +116,8 @@ public class TableColumnDescriptor { this.column = column; } - TableColumnInfo(DynamicTableColumn column, boolean isVisible, - int sortIndex, boolean ascending) { + TableColumnInfo(DynamicTableColumn column, boolean isVisible, int sortIndex, + boolean ascending) { this.column = column; this.isVisible = isVisible; this.sortIndex = sortIndex; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java index 4a81933014..d872eb70f4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/IncrementalLoadJob.java @@ -35,9 +35,11 @@ public class IncrementalLoadJob 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 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 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 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 extends Job implements ThreadedTable * is being provided to the accumulator. */ private class IncrementalUpdatingAccumulator extends SynchronizedListAccumulator { - 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 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 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 extends Job implements ThreadedTable } void flushData() { - cancelledOrDone = true; + isDone = true; swingUpdateManager.dispose(); updateManager.reloadSpecificData(asList()); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java index dce1b0641b..ba4e1cfe99 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/TableUpdateJob.java @@ -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. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java index a472d418f3..e771a70a37 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/threaded/ThreadedTableModel.java @@ -145,6 +145,10 @@ public abstract class ThreadedTableModel 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. diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/TestDataKeyModel.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/TestDataKeyModel.java index 267da4416a..7d0de22211 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/TestDataKeyModel.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/threaded/TestDataKeyModel.java @@ -36,27 +36,30 @@ public class TestDataKeyModel extends ThreadedTableModelStub { 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 { 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 loadJob = null; diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.java index 74ee22eeaa..15d9941577 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.java @@ -48,4 +48,9 @@ public class ConcurrentListenerSet implements Iterable { public List asList() { return new ArrayList<>(storage.keySet()); } + + @Override + public String toString() { + return asList().toString(); + } } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java index 5f680a3821..f7c1e45ea3 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQ.java @@ -41,14 +41,14 @@ import ghidra.util.task.TaskMonitor; * // do work here... * } * }; - * + * * ConcurrentQBuilder builder = new ConcurrentQBuilder(); * builder.setThreadPoolName("Thread Pool Name"); * concurrentQ = builder.getQueue(callback); * ... * ... * concurrentQ.add(item); // where item is one of the instances of ITEM - * + * * } *
*

@@ -59,14 +59,14 @@ import ghidra.util.task.TaskMonitor; * // do work here... * } * }; - * + * * {@literal QItemListener itemListener = new QItemListener()} { * {@literal public void itemProcessed(QResult result)} { * RESULT result = result.getResult(); * // work on my result... * } * }; - * + * * {@literal ConcurrentQBuilder builder = new ConcurrentQBuilder()}; * builder.setThreadPoolName("Thread Pool Name"); * builder.setListener(itemListener); @@ -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); - * + * * - * + * *


*

* Put Items and Handle Results When All Items Have Been Processed: @@ -99,10 +99,10 @@ import ghidra.util.task.TaskMonitor; * concurrentQ.add(item); * concurrentQ.add(item); * ... - * + * * {@literal List> results = concurrentQ.waitForResults();}{@literal * // process the results... - * + * * } *


*

@@ -120,7 +120,7 @@ import ghidra.util.task.TaskMonitor; * // work on my result... * } * }; - * + * * {@literal ConcurrentQBuilder builder = new ConcurrentQBuilder()}; * builder.setThreadPoolName("Thread Pool Name"); * builder.setQueue(new LinkedBlockingQueue(100)); @@ -129,10 +129,10 @@ import ghidra.util.task.TaskMonitor; * ... * {@literal Iterator iterator = } * {@code concurrentQ.offer(iterator); // this call will block when the queue fills up (100 items or more)} - * + * * *


- * + * * @param The type of the items to be processed. * @param 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 Object or the @@ -162,7 +162,7 @@ public class ConcurrentQ { /** * 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 { /** * 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 { /** * 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 listener) { @@ -239,7 +239,7 @@ public class ConcurrentQ { /** * 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 { *

* To enable blocking on the queue when it is full, construct this ConcurrentQ * 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 { *

* 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 { /** * 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 { * 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 collectResults 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 { *

* 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 { * 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 { if (threadPool.isPrivate()) { threadPool.shutdownNow(); } + + lock.lock(); + try { + resultList.clear(); + } + finally { + lock.unlock(); + } } public boolean waitUntilDone(long timeout, TimeUnit unit) throws InterruptedException { diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQBuilder.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQBuilder.java index 00030e9c41..61a1e8cd19 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQBuilder.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentQBuilder.java @@ -24,11 +24,11 @@ import ghidra.util.task.TaskMonitor; /** * A helper class to build up the potentially complicated {@link ConcurrentQ}. *

- * 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)}. - * + * *

* Examples: *

@@ -38,25 +38,25 @@ import ghidra.util.task.TaskMonitor; * // do work here... * } * }; - * + * * ConcurrentQBuilder builder = new ConcurrentQBuilder(); * builder.setThreadPoolName("Thread Pool Name"); * builder.setQueue(new PriorityBlockingQueue()); * concurrentQ = builder.build(callback); - * + * * // OR, you can chain the builder calls: * ConcurrentQBuilder builder = new ConcurrentQBuilder(); * queue = builder.setThreadPoolName("Thread Pool Name"). * setQueue(new PriorityBlockingQueue()). * setMaxInProgress(1). * build(callback); - * + * * } *

- * - * 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 The type of the items to be processed. @@ -74,14 +74,14 @@ public class ConcurrentQBuilder { 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}. *

- * 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 { } /** - * 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 { /** * Sets the name to be used when creating a private thread pool. If you wish to use - * a shared thread pool, then you need to create that thread pool youself and call + * a shared thread pool, 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 { /** * 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 { /** * 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 { } /** - * 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. *

* 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 { } /** - * 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 { public ConcurrentQ build(QCallback callback) { - ConcurrentQ concurrentQ = - new ConcurrentQ<>(callback, getQueue(), getThreadPool(), listener, collectResults, - maxInProgress, jobsReportProgress); + ConcurrentQ concurrentQ = new ConcurrentQ<>(callback, getQueue(), getThreadPool(), + listener, collectResults, maxInProgress, jobsReportProgress); if (monitor != null) { concurrentQ.setMonitor(monitor, cancelClearsAllJobs); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SynchronizedListAccumulator.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SynchronizedListAccumulator.java index 7397c1cab2..3ecd2fdb09 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SynchronizedListAccumulator.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SynchronizedListAccumulator.java @@ -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 implements Accumulator { return list.size(); } + public void clear() { + list.clear(); + } + @Override public synchronized Iterator iterator() { return asList().iterator(); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Job.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Job.java index a2b395c610..a7d035f210 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Job.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Job.java @@ -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; diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java index f4afb3dece..f6406b89d3 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java @@ -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"); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java index 934eecb614..3cd1517e95 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java @@ -633,7 +633,7 @@ public abstract class PluginTool extends AbstractDockingTool { } winMgr.restoreWindowDataFromXml(root); - winMgr.setToolName(fullName); + updateTitle(); return hasErrors; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java index e3153b95dc..68fd6d10bc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/StandAloneApplication.java @@ -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 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; }