mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-12 23:23:17 +00:00
Merge remote-tracking branch 'origin/GT-2875-dragonmacher-task-launcher-deadlock'
This commit is contained in:
commit
1340268ffa
@ -21,7 +21,7 @@ import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
@ -149,7 +149,7 @@ public class CreateDataBackgroundCmd extends BackgroundCommand {
|
||||
|
||||
// Allow the Swing thread a chance to paint components that may require
|
||||
// a DB lock.
|
||||
SystemUtilities.allowSwingToProcessEvents();
|
||||
Swing.allowSwingToProcessEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ public class MoveBlockTask extends ProgramTask {
|
||||
|
||||
@Override
|
||||
protected void doRun(TaskMonitor monitor) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
Memory mem = program.getMemory();
|
||||
MemoryBlock block = mem.getBlock(currentStart);
|
||||
@ -77,23 +76,23 @@ public class MoveBlockTask extends ProgramTask {
|
||||
}
|
||||
}
|
||||
catch (OutOfMemoryError e) {
|
||||
monitor.setMessage(msg = "Insufficient memory to complete operation");
|
||||
msg = "Insufficient memory to complete operation";
|
||||
cause = e;
|
||||
}
|
||||
catch (NotFoundException exc) {
|
||||
monitor.setMessage(msg = "Memory block not found");
|
||||
msg = "Memory block not found";
|
||||
cause = exc;
|
||||
}
|
||||
catch (MemoryConflictException exc) {
|
||||
monitor.setMessage(msg = exc.getMessage());
|
||||
msg = exc.getMessage();
|
||||
cause = exc;
|
||||
}
|
||||
catch (MemoryBlockException exc) {
|
||||
monitor.setMessage(msg = exc.getMessage());
|
||||
msg = exc.getMessage();
|
||||
cause = exc;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
monitor.setMessage(msg = e.getMessage());
|
||||
msg = e.getMessage();
|
||||
cause = e;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
@ -102,25 +101,18 @@ public class MoveBlockTask extends ProgramTask {
|
||||
if (msg == null) {
|
||||
msg = t.toString();
|
||||
}
|
||||
monitor.setMessage(msg);
|
||||
cause = t;
|
||||
}
|
||||
|
||||
monitor.setMessage(msg);
|
||||
listener.moveBlockCompleted(this);
|
||||
throw new RollbackException(msg, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user cancelled the move command.
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return wasCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the block was successfully moved.
|
||||
*
|
||||
* @return true if the block was moved
|
||||
*/
|
||||
public boolean getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
@ -184,7 +184,7 @@ public class ClearCmd extends BackgroundCommand {
|
||||
monitor.setProgress(progress);
|
||||
|
||||
// Allow Swing a chance to paint components that may require a DB lock
|
||||
SystemUtilities.allowSwingToProcessEvents();
|
||||
Swing.allowSwingToProcessEvents();
|
||||
}
|
||||
}
|
||||
previousRangeAddrCnt += range.getLength();
|
||||
@ -295,7 +295,7 @@ public class ClearCmd extends BackgroundCommand {
|
||||
AddressRangeIterator it = clearView.getAddressRanges();
|
||||
while (it.hasNext()) {
|
||||
|
||||
AddressRange currentRange = it.next();
|
||||
AddressRange currentRange = it.next();
|
||||
Address start = currentRange.getMinAddress();
|
||||
Address end = currentRange.getMaxAddress();
|
||||
clearAddresses(monitor, listing, start, end);
|
||||
@ -320,7 +320,7 @@ public class ClearCmd extends BackgroundCommand {
|
||||
monitor.incrementProgress(numDone);
|
||||
|
||||
// Allow the Swing thread a chance to paint components that may require a DB lock
|
||||
SystemUtilities.allowSwingToProcessEvents();
|
||||
Swing.allowSwingToProcessEvents();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,11 @@ public class CreateTypeDefAction extends AbstractTypeDefAction {
|
||||
}
|
||||
|
||||
ArchiveNode archiveNode = node.getArchiveNode();
|
||||
if (archiveNode == null) {
|
||||
// this can happen as the tree is changing
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean enabled = archiveNode.isModifiable();
|
||||
if (archiveNode instanceof BuiltInArchiveNode) {
|
||||
// these will be put into the program archive
|
||||
|
@ -100,7 +100,8 @@ public class DisassociateAction extends DockingAction {
|
||||
monitor -> doDisassociate(synchronizer, typesToDisassociate, allAssociatedTypes, monitor);
|
||||
new TaskBuilder("Disassociate From Archive", r)
|
||||
.setStatusTextAlignment(SwingConstants.LEADING)
|
||||
.launchModal();
|
||||
.launchModal()
|
||||
;
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
|
@ -103,8 +103,12 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
|
||||
return;
|
||||
}
|
||||
|
||||
new TaskLauncher(new SyncTask(synchronizer), null, TaskLauncher.INITIAL_MODAL_DELAY,
|
||||
SwingConstants.LEFT);
|
||||
//@formatter:off
|
||||
TaskBuilder.withTask(new SyncTask(synchronizer))
|
||||
.setStatusTextAlignment(SwingConstants.LEADING)
|
||||
.launchModal()
|
||||
;
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
private void doSync(DataTypeSynchronizer synchronizer, TaskMonitor monitor) {
|
||||
|
@ -26,10 +26,10 @@ import docking.action.DockingAction;
|
||||
import docking.action.ToolBarData;
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.HelpLocation;
|
||||
import resources.Icons;
|
||||
import resources.ResourceManager;
|
||||
import utility.function.Callback;
|
||||
|
||||
public class InterpreterComponentProvider extends ComponentProviderAdapter
|
||||
implements InterpreterConsole {
|
||||
|
@ -18,8 +18,8 @@ package ghidra.app.plugin.core.interpreter;
|
||||
import java.io.*;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.Disposable;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Interactive interpreter console.
|
||||
|
@ -18,8 +18,6 @@ package ghidra.app.plugin.core.memory;
|
||||
import java.awt.Cursor;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.widgets.label.GDLabel;
|
||||
@ -32,7 +30,7 @@ import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.task.BackgroundThreadTaskLauncher;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
|
||||
/**
|
||||
@ -77,22 +75,19 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
|
||||
*/
|
||||
@Override
|
||||
public void moveBlockCompleted(final MoveBlockTask cmd) {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cmd.getStatus()) {
|
||||
Runnable r = () -> {
|
||||
if (cmd.getStatus()) {
|
||||
close();
|
||||
model.dispose();
|
||||
}
|
||||
else {
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
setOkEnabled(false);
|
||||
if (cmd.isCancelled()) {
|
||||
tool.setStatusInfo(getStatusText());
|
||||
close();
|
||||
model.dispose();
|
||||
}
|
||||
else {
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
setOkEnabled(false);
|
||||
if (cmd.isCancelled()) {
|
||||
tool.setStatusInfo(getStatusText());
|
||||
close();
|
||||
model.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
SwingUtilities.invokeLater(r);
|
||||
@ -142,7 +137,9 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
|
||||
protected void okCallback() {
|
||||
setOkEnabled(false);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
new TaskLauncher(model.makeTask(), new TaskMonitorAdapter() {
|
||||
|
||||
BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(model.makeTask());
|
||||
launcher.run(new TaskMonitorAdapter() {
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
setStatusText(message);
|
||||
@ -176,18 +173,8 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
|
||||
newEndField = new AddressInput();
|
||||
newEndField.setName("newEnd");
|
||||
|
||||
newStartField.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
startChanged();
|
||||
}
|
||||
});
|
||||
newEndField.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
endChanged();
|
||||
}
|
||||
});
|
||||
newStartField.addChangeListener(e -> startChanged());
|
||||
newEndField.addChangeListener(e -> endChanged());
|
||||
|
||||
panel.add(new GLabel("Name:", SwingConstants.RIGHT));
|
||||
panel.add(blockNameLabel);
|
||||
|
@ -21,6 +21,13 @@
|
||||
|
||||
package ghidra.app.plugin.core.module;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.MenuData;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
@ -36,14 +43,6 @@ import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.MenuData;
|
||||
|
||||
/**
|
||||
* Plugin to sort Modules and Fragments within a selected Module.
|
||||
* Child Module folders are always name-sorted and placed
|
||||
@ -97,10 +96,12 @@ public class ModuleSortPlugin extends ProgramPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
SortTask sortTask = new SortTask(module, sortType);
|
||||
|
||||
// this blocks until the task is finished
|
||||
new TaskLauncher(sortTask, null, TaskLauncher.INITIAL_MODAL_DELAY, SwingConstants.LEADING);
|
||||
//@formatter:off
|
||||
TaskBuilder.withTask(new SortTask(module, sortType))
|
||||
.setStatusTextAlignment(SwingConstants.LEADING)
|
||||
.launchModal()
|
||||
;
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
private void doSort(ProgramModule parent, GroupComparator comparator, TaskMonitor monitor)
|
||||
@ -110,11 +111,11 @@ public class ModuleSortPlugin extends ProgramPlugin {
|
||||
|
||||
monitor.initialize(kids.length);
|
||||
|
||||
for (int i = 0; i < kids.length; i++) {
|
||||
for (Group kid : kids) {
|
||||
monitor.checkCanceled();
|
||||
list.add(kids[i]);
|
||||
if (kids[i] instanceof ProgramModule) {
|
||||
doSort((ProgramModule) kids[i], comparator, monitor);
|
||||
list.add(kid);
|
||||
if (kid instanceof ProgramModule) {
|
||||
doSort((ProgramModule) kid, comparator, monitor);
|
||||
}
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
@ -152,8 +153,9 @@ public class ModuleSortPlugin extends ProgramPlugin {
|
||||
private ProgramModule getSelectedModule(Object contextObj) {
|
||||
if (contextObj instanceof ProgramNode) {
|
||||
ProgramNode node = (ProgramNode) contextObj;
|
||||
if (node.isModule() && node.getTree().getSelectionCount() == 1)
|
||||
if (node.isModule() && node.getTree().getSelectionCount() == 1) {
|
||||
return node.getModule();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -244,12 +246,14 @@ public class ModuleSortPlugin extends ProgramPlugin {
|
||||
this.sortType = sortType;
|
||||
if (sortType == SORT_BY_ADDRESS) {
|
||||
setPopupMenuData(new MenuData(SORT_BY_ADDR_MENUPATH, null, "module"));
|
||||
setDescription("Perform a minimum address sort of all fragments contained within a selected folder");
|
||||
setDescription(
|
||||
"Perform a minimum address sort of all fragments contained within a selected folder");
|
||||
}
|
||||
else {
|
||||
setPopupMenuData(new MenuData(SORT_BY_NAME_MENUPATH, null, "module"));
|
||||
|
||||
setDescription("Perform a name sort of all fragments contained within a selected folder");
|
||||
setDescription(
|
||||
"Perform a name sort of all fragments contained within a selected folder");
|
||||
}
|
||||
setEnabled(true); // always enabled
|
||||
setHelpLocation(new HelpLocation("ProgramTreePlugin", "SortByAddressOrName"));
|
||||
@ -258,7 +262,7 @@ public class ModuleSortPlugin extends ProgramPlugin {
|
||||
/**
|
||||
* Determine if the Module Sort action should be visible within
|
||||
* the popup menu for the specified active object.
|
||||
* @param activeObj the object under the mouse location for the popup.
|
||||
* @param context the context
|
||||
* @return true if action should be made visible in popup menu.
|
||||
*/
|
||||
@Override
|
||||
|
@ -33,7 +33,6 @@ import ghidra.app.nav.NavigatableRemovalListener;
|
||||
import ghidra.app.services.GoToService;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
@ -44,6 +43,7 @@ import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.table.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import resources.ResourceManager;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Dialog to show a table of items. If the dialog is constructed with a non-null
|
||||
|
@ -49,7 +49,6 @@ import ghidra.app.context.ListingActionContext;
|
||||
*/
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.program.database.symbol.EquateManager;
|
||||
import ghidra.program.model.data.DataTypeManager;
|
||||
import ghidra.program.model.data.Enum;
|
||||
@ -62,6 +61,7 @@ import ghidra.util.UniversalID;
|
||||
import ghidra.util.layout.HorizontalLayout;
|
||||
import ghidra.util.layout.VerticalLayout;
|
||||
import ghidra.util.table.*;
|
||||
import utility.function.Callback;
|
||||
|
||||
public class SetEquateDialog extends DialogComponentProvider {
|
||||
public static final int CANCELED = 0;
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.test;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -32,7 +32,6 @@ import ghidra.framework.cmd.Command;
|
||||
import ghidra.framework.model.UndoableDomainObject;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.generic.function.*;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.*;
|
||||
@ -47,6 +46,7 @@ import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.RollbackException;
|
||||
import junit.framework.AssertionFailedError;
|
||||
import utility.application.ApplicationLayout;
|
||||
import utility.function.*;
|
||||
|
||||
public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDockingTest {
|
||||
|
||||
|
@ -25,13 +25,13 @@ import org.junit.After;
|
||||
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.generic.function.ExceptionalConsumer;
|
||||
import ghidra.generic.function.ExceptionalFunction;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import util.CollectionUtils;
|
||||
import utility.function.ExceptionalConsumer;
|
||||
import utility.function.ExceptionalFunction;
|
||||
|
||||
/**
|
||||
* A convenience base class for creating tests that use the default tool and open a program.
|
||||
|
@ -35,7 +35,6 @@ import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
@ -51,6 +50,7 @@ import ghidra.util.TrackedTaskListener;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.field.AddressBasedLocation;
|
||||
import ghidra.util.task.Task;
|
||||
import utility.function.Callback;
|
||||
|
||||
public class AutoTableDisassemblerTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
|
@ -21,7 +21,6 @@ import org.junit.*;
|
||||
|
||||
import ghidra.app.cmd.memory.MoveBlockListener;
|
||||
import ghidra.app.cmd.memory.MoveBlockTask;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.database.data.DataTypeManagerDB;
|
||||
@ -33,32 +32,18 @@ import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
/**
|
||||
* Test the model that moves a block of memory.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
implements MoveBlockListener {
|
||||
|
||||
private Program notepad;
|
||||
private Program x8051;
|
||||
private PluginTool tool;
|
||||
private TestEnv env;
|
||||
private MoveBlockModel model;
|
||||
private MemoryBlock block;
|
||||
private boolean expectedStatus;
|
||||
private boolean moveCompleted;
|
||||
private boolean status;
|
||||
private String errMsg;
|
||||
|
||||
/**
|
||||
* Constructor for MoveBlockModelTest.
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public MoveBlockModelTest() {
|
||||
super();
|
||||
}
|
||||
private volatile boolean moveCompleted;
|
||||
private volatile boolean status;
|
||||
private volatile String errMsg;
|
||||
|
||||
private Program buildProgram1(String programName) throws Exception {
|
||||
ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY);
|
||||
@ -84,13 +69,10 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
return builder.getProgram();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see TestCase#setUp()
|
||||
*/
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
|
||||
notepad = buildProgram1("notepad");
|
||||
x8051 = buildProgram2("x08");
|
||||
block = notepad.getMemory().getBlock(getNotepadAddr(0x1001000));
|
||||
@ -109,13 +91,8 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
x8051.endTransaction(transactionID, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see TestCase#tearDown()
|
||||
*/
|
||||
@After
|
||||
public void tearDown() {
|
||||
env.release(x8051);
|
||||
env.release(notepad);
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
@ -152,25 +129,23 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
@Test
|
||||
public void testMoveBlockStart() throws Exception {
|
||||
model.setNewStartAddress(getNotepadAddr(0x2000000));
|
||||
expectedStatus = true;
|
||||
|
||||
launch(model.makeTask());
|
||||
|
||||
// wait until the we get the move complete notification
|
||||
while (!moveCompleted || !notepad.canLock()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status);
|
||||
waitForCondition(() -> moveCompleted && notepad.canLock());
|
||||
assertTrue("Error message= [" + errMsg + "], ", status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveBlockEnd() throws Exception {
|
||||
model.setNewEndAddress(getNotepadAddr(0x2007500));
|
||||
expectedStatus = true;
|
||||
|
||||
launch(model.makeTask());
|
||||
|
||||
// wait until the we get the move complete notification
|
||||
while (!moveCompleted || !notepad.canLock()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status);
|
||||
waitForCondition(() -> moveCompleted && notepad.canLock());
|
||||
assertTrue("Error message= [" + errMsg + "], ", status);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -194,13 +169,15 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
start = getAddr(x8051, "INTMEM", 0x50);
|
||||
model.setNewStartAddress(start);
|
||||
assertEquals(getAddr(x8051, "INTMEM", 0xcf), model.getNewEndAddress());
|
||||
expectedStatus = false;
|
||||
|
||||
setErrorsExpected(true);
|
||||
launch(model.makeTask());
|
||||
|
||||
// wait until the we get the move complete notification
|
||||
while (!moveCompleted || !x8051.canLock()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status);
|
||||
waitForCondition(() -> moveCompleted && x8051.canLock());
|
||||
setErrorsExpected(false);
|
||||
|
||||
assertFalse("Error message= [" + errMsg + "], ", status);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -213,13 +190,12 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
model.initialize(block);
|
||||
start = getAddr(x8051, "CODE", 0x2000);
|
||||
model.setNewStartAddress(start);
|
||||
expectedStatus = true;
|
||||
moveCompleted = false;
|
||||
|
||||
launch(model.makeTask());
|
||||
|
||||
// wait until the we get the move complete notification
|
||||
while (!moveCompleted || !x8051.canLock()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
waitForCondition(() -> moveCompleted && x8051.canLock());
|
||||
|
||||
// make sure settings on data got moved
|
||||
DataTypeManagerDB dtm = ((ProgramDB) x8051).getDataManager();
|
||||
|
||||
@ -257,19 +233,19 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
Address newStart = memBlock.getStart().getNewAddress(0x01002000);
|
||||
model.setNewStartAddress(newStart);
|
||||
|
||||
expectedStatus = false;
|
||||
errMsg = null;
|
||||
|
||||
setErrorsExpected(true);
|
||||
launch(model.makeTask());
|
||||
while (!moveCompleted || !notepad.canLock()) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
assertTrue(!expectedStatus);
|
||||
|
||||
waitForCondition(() -> moveCompleted && notepad.canLock());
|
||||
setErrorsExpected(false);
|
||||
|
||||
assertNotNull(errMsg);
|
||||
}
|
||||
|
||||
private void launch(Task task) {
|
||||
new TaskLauncher(task, new TaskMonitorAdapter() {
|
||||
|
||||
BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task);
|
||||
launcher.run(new TaskMonitorAdapter() {
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
errMsg = message;
|
||||
@ -286,21 +262,14 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
|
||||
return space.getAddress(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.app.plugin.contrib.memory.MoveBlockListener#moveBlockCompleted(boolean,
|
||||
* java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void moveBlockCompleted(MoveBlockTask cmd) {
|
||||
moveCompleted = true;
|
||||
this.status = cmd.getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.app.plugin.contrib.memory.MoveBlockListener#stateChanged()
|
||||
*/
|
||||
@Override
|
||||
public void stateChanged() {
|
||||
// stub
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,10 +32,10 @@ import docking.event.mouse.GMouseListenerAdapter;
|
||||
import docking.menu.DockingToolbarButton;
|
||||
import docking.util.*;
|
||||
import docking.widgets.label.GDHtmlLabel;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.task.*;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Base class used for creating dialogs in Ghidra. Subclass this to create a dialog provider that has
|
||||
|
@ -15,8 +15,7 @@
|
||||
*/
|
||||
package docking.test;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.*;
|
||||
@ -1760,9 +1759,11 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
|
||||
*/
|
||||
public static void setErrorsExpected(boolean expected) {
|
||||
if (expected) {
|
||||
Msg.error(AbstractDockingTest.class, ">>>>>>>>>>>>>>>> Expected Exception");
|
||||
ConcurrentTestExceptionHandler.disable();
|
||||
}
|
||||
else {
|
||||
Msg.error(AbstractDockingTest.class, "<<<<<<<<<<<<<<<< End Expected Exception");
|
||||
ConcurrentTestExceptionHandler.enable();
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* A text field that is meant to be used in conjunction with tables that allow filter text. This
|
||||
|
@ -28,10 +28,10 @@ import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||
import ghidra.util.filechooser.GhidraFileFilter;
|
||||
import resources.ResourceManager;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Component that has a table to show pathnames; the panel includes buttons to control
|
||||
|
@ -40,7 +40,6 @@ import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
|
||||
import docking.widgets.table.columnfilter.ColumnFilterSaveManager;
|
||||
import docking.widgets.table.constraint.dialog.ColumnFilterDialog;
|
||||
import ghidra.framework.options.PreferenceState;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
@ -50,6 +49,7 @@ import ghidra.util.task.SwingUpdateManager;
|
||||
import resources.Icons;
|
||||
import resources.ResourceManager;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* This class is a panel that provides a label and text field that allows users to input text that
|
||||
|
@ -33,11 +33,11 @@ import docking.widgets.table.GTableFilterPanel;
|
||||
import docking.widgets.table.RowObjectFilterModel;
|
||||
import docking.widgets.table.columnfilter.*;
|
||||
import docking.widgets.table.constrainteditor.ColumnConstraintEditor;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.layout.VerticalLayout;
|
||||
import resources.ResourceManager;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Dialog for creating and editing column table filters.
|
||||
|
@ -221,7 +221,8 @@ public abstract class DataLoadingConstraintEditor<T> extends AbstractColumnConst
|
||||
reloadDataButton.setVisible(false);
|
||||
Task task = new LoadDataTask();
|
||||
task.addTaskListener(this);
|
||||
new TaskLauncher(task, (TaskMonitor) taskMonitorComponent);
|
||||
BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task);
|
||||
launcher.run(taskMonitorComponent);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,53 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import generic.concurrent.GThreadPool;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.TaskUtilities;
|
||||
|
||||
/**
|
||||
* Helper class to launch the given task in a background thread This helper will not
|
||||
* show a task dialog.
|
||||
*
|
||||
* <p>This class is useful when you want to run the task and use a monitor that is embedded
|
||||
* in some other component.
|
||||
*
|
||||
* <p>See {@link TaskLauncher}.
|
||||
*/
|
||||
public class BackgroundThreadTaskLauncher {
|
||||
|
||||
private Task task;
|
||||
|
||||
public BackgroundThreadTaskLauncher(Task task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public void run(TaskMonitor monitor) {
|
||||
// add the task here, so we can track it before it is actually started by the thread
|
||||
TaskUtilities.addTrackedTask(task, monitor);
|
||||
|
||||
String name = "Task - " + task.getTaskTitle();
|
||||
GThreadPool pool = GThreadPool.getSharedThreadPool(Swing.GSWING_THREAD_POOL_NAME);
|
||||
Executor executor = pool.getExecutor();
|
||||
executor.execute(() -> {
|
||||
Thread.currentThread().setName(name);
|
||||
task.monitoredRun(monitor);
|
||||
});
|
||||
}
|
||||
}
|
@ -54,6 +54,19 @@ import util.CollectionUtils;
|
||||
* .launchModal();
|
||||
* </pre>
|
||||
*
|
||||
* Or,
|
||||
*
|
||||
* <pre>
|
||||
* TaskBuilder.withTask(new AwesomeTask(awesomeStuff)).launchModal();
|
||||
* </pre>
|
||||
*
|
||||
* Or,
|
||||
*
|
||||
* <pre>
|
||||
* {@link TaskLauncher#launch(Task) TaskLauncher.launch}(new AwesomeTask(awesomeStuff));
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <p>Note: this class will check to see if it is in a headless environment before launching
|
||||
* its task. This makes it safe to use this class in headed or headless environments.
|
||||
*/
|
||||
@ -84,6 +97,21 @@ public class TaskBuilder {
|
||||
return new TaskBuilder(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to start a builder using the given task. The
|
||||
* {@link #setTitle(String) title} of the task will be the value of
|
||||
* {@link Task#getTaskTitle()}.
|
||||
*
|
||||
* <p>This method allows for a more attractive fluent API usage than does the constructor
|
||||
* (see the javadoc header).
|
||||
*
|
||||
* @param t the task
|
||||
* @return this builder
|
||||
*/
|
||||
public static TaskBuilder withTask(Task t) {
|
||||
return new TaskBuilder(t.getTaskTitle(), t);
|
||||
}
|
||||
|
||||
private TaskBuilder(MonitoredRunnable r) {
|
||||
this.runnable = Objects.requireNonNull(r);
|
||||
}
|
||||
@ -148,8 +176,8 @@ public class TaskBuilder {
|
||||
|
||||
/**
|
||||
* Sets the amount of time that will pass before showing the dialog. The default is
|
||||
* {@link TaskLauncher#INITIAL_DELAY} for non-modal tasks and
|
||||
* {@link TaskLauncher#INITIAL_MODAL_DELAY} for modal tasks.
|
||||
* {@link TaskLauncher#INITIAL_DELAY_MS} for non-modal tasks and
|
||||
* {@link TaskLauncher#INITIAL_MODAL_DELAY_MS} for modal tasks.
|
||||
*
|
||||
* @param delay the delay time
|
||||
* @return this builder
|
||||
@ -239,9 +267,9 @@ public class TaskBuilder {
|
||||
}
|
||||
|
||||
if (isModal) {
|
||||
return TaskLauncher.INITIAL_MODAL_DELAY;
|
||||
return TaskLauncher.INITIAL_MODAL_DELAY_MS;
|
||||
}
|
||||
return TaskLauncher.INITIAL_DELAY;
|
||||
return TaskLauncher.INITIAL_DELAY_MS;
|
||||
}
|
||||
|
||||
private class TaskBuilderTask extends Task {
|
||||
|
@ -16,24 +16,30 @@
|
||||
package ghidra.util.task;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import generic.util.WindowUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.TaskUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.UnableToSwingException;
|
||||
|
||||
/**
|
||||
* Class to initiate a Task in a new Thread, and to show a progress dialog that indicates
|
||||
* activity. The progress dialog will show an animation in the event that the task of this class
|
||||
* cannot show progress.
|
||||
* activity <b>if the task takes too long</b>. The progress dialog will show an
|
||||
* animation in the event that the task of this class cannot show progress.
|
||||
*
|
||||
* <p>For complete control of how this class functions, use
|
||||
* {@link #TaskLauncher(Task, Component, int, int)}. Alternatively, for simpler uses,
|
||||
* see one of the many static convenience methods.
|
||||
*
|
||||
* <p><b>Important Usage Note:</b><br>
|
||||
* For clients that are not on the Swing thread the behavior of this class is designed to
|
||||
* prevent deadlocks. When called from a non-Swing thread, this class will attempt to show a
|
||||
* modal dialog. However, if more than {@link #getSwingTimeoutInSeconds()} elapses while waiting
|
||||
* for the Swing thread, then this class will <b>give up on using the Swing thread and will not
|
||||
* create a background thread</b>. Instead, the client code will be run in the client thread.
|
||||
*
|
||||
* <a name="modal_usage"></a>
|
||||
* <p><a name="modal_usage"></a>Most clients of this class should not be concerned with where
|
||||
* <p><b><a name="modal_usage">Modal Usage</a></b><br>
|
||||
* Most clients of this class should not be concerned with where
|
||||
* the dialog used by this class will appear. By default, it will be shown over
|
||||
* the active window, which is the desired
|
||||
* behavior for most uses. If you should need a dialog to appear over a non-active window,
|
||||
@ -93,7 +99,7 @@ public class TaskLauncher {
|
||||
public void run(TaskMonitor monitor) {
|
||||
runnable.monitoredRun(monitor);
|
||||
}
|
||||
}, null, INITIAL_DELAY);
|
||||
}, null, INITIAL_DELAY_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,7 +128,7 @@ public class TaskLauncher {
|
||||
public void run(TaskMonitor monitor) {
|
||||
runnable.monitoredRun(monitor);
|
||||
}
|
||||
}, null, INITIAL_MODAL_DELAY);
|
||||
}, null, INITIAL_MODAL_DELAY_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,33 +169,13 @@ public class TaskLauncher {
|
||||
// End Static Launcher Methods
|
||||
//==================================================================================================
|
||||
|
||||
static final int INITIAL_DELAY = 1000;// 1 second
|
||||
static final int INITIAL_DELAY_MS = 1000;
|
||||
|
||||
/** The time, for modal tasks, to try and run before blocking and showing a dialog */
|
||||
public static final int INITIAL_MODAL_DELAY = 500;
|
||||
|
||||
protected Task task;
|
||||
private TaskDialog taskDialog;
|
||||
private Thread taskThread;
|
||||
private CancelledListener monitorChangeListener = () -> {
|
||||
if (task.isInterruptible()) {
|
||||
taskThread.interrupt();
|
||||
}
|
||||
if (task.isForgettable()) {
|
||||
taskDialog.close(); // close the dialog and forget about the task
|
||||
}
|
||||
};
|
||||
|
||||
private static Component getParent(Component parent) {
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (parent.isVisible() ? parent : null);
|
||||
}
|
||||
static final int INITIAL_MODAL_DELAY_MS = 500;
|
||||
|
||||
/**
|
||||
* Constructor for TaskLauncher.
|
||||
* Constructor for TaskLauncher
|
||||
*
|
||||
* <p>This constructor assumes that if a progress dialog is needed, then it should appear
|
||||
* over the active window. If you should need a dialog to appear over a non-active window,
|
||||
@ -200,11 +186,11 @@ public class TaskLauncher {
|
||||
*
|
||||
*/
|
||||
public TaskLauncher(Task task) {
|
||||
this(task, null, task.isModal() ? INITIAL_MODAL_DELAY : INITIAL_DELAY);
|
||||
this(task, null, task.isModal() ? INITIAL_MODAL_DELAY_MS : INITIAL_DELAY_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for TaskLauncher.
|
||||
* Constructor for TaskLauncher
|
||||
*
|
||||
* <p>See <a href="#modal_usage">notes on modal usage</a>
|
||||
*
|
||||
@ -212,121 +198,96 @@ public class TaskLauncher {
|
||||
* @param parent component whose window to use to parent the dialog.
|
||||
*/
|
||||
public TaskLauncher(Task task, Component parent) {
|
||||
this(task, getParent(parent), task.isModal() ? INITIAL_MODAL_DELAY : INITIAL_DELAY);
|
||||
this(task, getParent(parent), task.isModal() ? INITIAL_MODAL_DELAY_MS : INITIAL_DELAY_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new TaskLauncher.
|
||||
* Construct a new TaskLauncher
|
||||
*
|
||||
* <p>See <a href="#modal_usage">notes on modal usage</a>
|
||||
*
|
||||
* @param task task to run in another thread (other than the Swing Thread)
|
||||
* @param parent component whose window to use to parent the dialog; null centers the task
|
||||
* dialog over the current window
|
||||
* @param delay number of milliseconds to delay until the task monitor is displayed
|
||||
* @param delayMs number of milliseconds to delay until the task monitor is displayed
|
||||
*/
|
||||
public TaskLauncher(Task task, Component parent, int delay) {
|
||||
this(task, parent, delay, TaskDialog.DEFAULT_WIDTH);
|
||||
public TaskLauncher(Task task, Component parent, int delayMs) {
|
||||
this(task, parent, delayMs, TaskDialog.DEFAULT_WIDTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new TaskLauncher.
|
||||
* Construct a new TaskLauncher
|
||||
*
|
||||
* <p>See <a href="#modal_usage">notes on modal usage</a>
|
||||
*
|
||||
* @param task task to run in another thread (other than the Swing Thread)
|
||||
* @param parent component whose window to use to parent the dialog; null centers the task
|
||||
* dialog over the current window
|
||||
* @param delay number of milliseconds to delay until the task monitor is displayed
|
||||
* @param delayMs number of milliseconds to delay until the task monitor is displayed
|
||||
* @param dialogWidth The preferred width of the dialog (this allows clients to make a wider
|
||||
* dialog, which better shows long messages).
|
||||
*/
|
||||
public TaskLauncher(Task task, Component parent, int delay, int dialogWidth) {
|
||||
public TaskLauncher(Task task, Component parent, int delayMs, int dialogWidth) {
|
||||
|
||||
this.task = task;
|
||||
this.taskDialog = buildTaskDialog(parent, dialogWidth);
|
||||
|
||||
startBackgroundThread(taskDialog);
|
||||
|
||||
taskDialog.show(Math.max(delay, 0));
|
||||
|
||||
waitForModalIfNotSwing();
|
||||
try {
|
||||
scheduleFromSwingThread(task, parent, delayMs, dialogWidth);
|
||||
}
|
||||
catch (UnableToSwingException e) {
|
||||
runInThisBackgroundThread(task);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForModalIfNotSwing() {
|
||||
if (SwingUtilities.isEventDispatchThread() || !task.isModal()) {
|
||||
private void scheduleFromSwingThread(Task task, Component parent, int delayMs, int dialogWidth)
|
||||
throws UnableToSwingException {
|
||||
|
||||
TaskRunner runner = createTaskRunner(task, parent, delayMs, dialogWidth);
|
||||
if (Swing.isEventDispatchThread()) {
|
||||
runner.run();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
taskThread.join();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Msg.debug(this, "Task Launcher unexpectedly interrupted waiting for task thread", e);
|
||||
}
|
||||
//
|
||||
// Not on the Swing thread. Try to execute on the Swing thread, timing-out if it takes
|
||||
// too long (this prevents deadlocks).
|
||||
//
|
||||
|
||||
// This will throw an exception if we could not get the Swing lock. When that happens,
|
||||
// the task was NOT run.
|
||||
int timeout = getSwingTimeoutInSeconds();
|
||||
Swing.runNow(() -> runner.run(), timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// template method to allow timeout change; used by tests
|
||||
protected int getSwingTimeoutInSeconds() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// template method to allow task runner change; used by tests
|
||||
protected TaskRunner createTaskRunner(Task task, Component parent, int delayMs,
|
||||
int dialogWidth) {
|
||||
return new TaskRunner(task, parent, delayMs, dialogWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor where an external taskMonitor is used. Normally, this class will provide
|
||||
* the {@link TaskDialog} as the monitor. This constructor is useful when you want to run
|
||||
* the task and use a monitor that is embedded in some other component.
|
||||
*
|
||||
* <p>See <a href="#modal_usage">notes on modal usage</a>
|
||||
*
|
||||
* @param task task to run in another thread (other than the Swing Thread)
|
||||
* @param taskMonitor the monitor to use while running the task.
|
||||
* Runs the given task in the current thread, which <b>cannot be the Swing thread</b>
|
||||
*
|
||||
* @param task the task to run
|
||||
* @throws IllegalStateException if the given thread is the Swing thread
|
||||
*/
|
||||
public TaskLauncher(Task task, TaskMonitor taskMonitor) {
|
||||
|
||||
this.task = task;
|
||||
|
||||
startBackgroundThread(taskMonitor);
|
||||
|
||||
}
|
||||
|
||||
private TaskDialog buildTaskDialog(Component comp, int dialogWidth) {
|
||||
|
||||
taskDialog = createTaskDialog(comp);
|
||||
taskDialog.setMinimumSize(dialogWidth, 0);
|
||||
|
||||
if (task.isInterruptible() || task.isForgettable()) {
|
||||
taskDialog.addCancelledListener(monitorChangeListener);
|
||||
protected void runInThisBackgroundThread(Task task) {
|
||||
if (Swing.isEventDispatchThread()) {
|
||||
throw new IllegalStateException("Must not call this method from the Swing thread");
|
||||
}
|
||||
|
||||
taskDialog.setStatusJustification(task.getStatusTextAlignment());
|
||||
|
||||
return taskDialog;
|
||||
task.monitoredRun(TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
private void startBackgroundThread(TaskMonitor monitor) {
|
||||
|
||||
// add the task here, so we can track it before it is actually started by the thread
|
||||
TaskUtilities.addTrackedTask(task, monitor);
|
||||
|
||||
String name = "Task - " + task.getTaskTitle();
|
||||
taskThread = new Thread(() -> {
|
||||
task.monitoredRun(monitor);
|
||||
taskProcessed();
|
||||
}, name);
|
||||
taskThread.setPriority(Thread.MIN_PRIORITY);
|
||||
taskThread.start();
|
||||
}
|
||||
|
||||
private void taskProcessed() {
|
||||
if (taskDialog != null) {
|
||||
taskDialog.taskProcessed();
|
||||
}
|
||||
}
|
||||
|
||||
protected TaskDialog createTaskDialog(Component comp) {
|
||||
Component parent = comp;
|
||||
if (parent != null) {
|
||||
parent = WindowUtilities.windowForComponent(comp);
|
||||
}
|
||||
|
||||
private static Component getParent(Component parent) {
|
||||
if (parent == null) {
|
||||
return new TaskDialog(task);
|
||||
return null;
|
||||
}
|
||||
return new TaskDialog(comp, task);
|
||||
|
||||
return (parent.isVisible() ? parent : null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,112 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import generic.concurrent.GThreadPool;
|
||||
import generic.util.WindowUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.TaskUtilities;
|
||||
|
||||
/**
|
||||
* Helper class to launch the given task in a background thread, showing a task dialog if
|
||||
* this task takes to long. See {@link TaskLauncher}.
|
||||
*/
|
||||
class TaskRunner {
|
||||
|
||||
protected Task task;
|
||||
private Component parent;
|
||||
private int delayMs;
|
||||
private int dialogWidth;
|
||||
private TaskDialog taskDialog;
|
||||
private Thread taskThread;
|
||||
private CancelledListener monitorChangeListener = () -> {
|
||||
if (task.isInterruptible()) {
|
||||
taskThread.interrupt();
|
||||
}
|
||||
if (task.isForgettable()) {
|
||||
taskDialog.close(); // close the dialog and forget about the task
|
||||
}
|
||||
};
|
||||
|
||||
TaskRunner(Task task, Component parent, int delayMs, int dialogWidth) {
|
||||
this.task = task;
|
||||
this.parent = parent;
|
||||
this.delayMs = delayMs;
|
||||
this.dialogWidth = dialogWidth;
|
||||
}
|
||||
|
||||
void run() {
|
||||
|
||||
// note: we need to be on the Swing thread to create our UI widgets
|
||||
Swing.assertThisIsTheSwingThread(
|
||||
"The Task runner is required to be run from the Swing thread");
|
||||
|
||||
this.taskDialog = buildTaskDialog(parent);
|
||||
|
||||
startBackgroundThread(taskDialog);
|
||||
|
||||
taskDialog.show(Math.max(delayMs, 0));
|
||||
}
|
||||
|
||||
protected TaskDialog buildTaskDialog(Component comp) {
|
||||
|
||||
//
|
||||
// This class may be used by background threads. Make sure that our GUI creation is
|
||||
// on the Swing thread to prevent exceptions while painting (as seen when using the
|
||||
// Nimbus Look and Feel).
|
||||
//
|
||||
taskDialog = createTaskDialog(comp);
|
||||
taskDialog.setMinimumSize(dialogWidth, 0);
|
||||
|
||||
if (task.isInterruptible() || task.isForgettable()) {
|
||||
taskDialog.addCancelledListener(monitorChangeListener);
|
||||
}
|
||||
|
||||
taskDialog.setStatusJustification(task.getStatusTextAlignment());
|
||||
|
||||
return taskDialog;
|
||||
}
|
||||
|
||||
private void startBackgroundThread(TaskMonitor monitor) {
|
||||
|
||||
// add the task here, so we can track it before it is actually started by the thread
|
||||
TaskUtilities.addTrackedTask(task, monitor);
|
||||
|
||||
String name = "Task - " + task.getTaskTitle();
|
||||
GThreadPool pool = GThreadPool.getSharedThreadPool(Swing.GSWING_THREAD_POOL_NAME);
|
||||
Executor executor = pool.getExecutor();
|
||||
executor.execute(() -> {
|
||||
Thread.currentThread().setName(name);
|
||||
task.monitoredRun(monitor);
|
||||
taskDialog.taskProcessed();
|
||||
});
|
||||
}
|
||||
|
||||
private TaskDialog createTaskDialog(Component comp) {
|
||||
Component currentParent = comp;
|
||||
if (currentParent != null) {
|
||||
currentParent = WindowUtilities.windowForComponent(comp);
|
||||
}
|
||||
|
||||
if (currentParent == null) {
|
||||
return new TaskDialog(task);
|
||||
}
|
||||
return new TaskDialog(comp, task);
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.generic.function.Callback;
|
||||
import utility.function.Callback;
|
||||
|
||||
public class FilterTextFieldTest {
|
||||
|
||||
|
@ -0,0 +1,207 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
|
||||
public class AbstractTaskTest extends AbstractDockingTest {
|
||||
|
||||
protected static final int DELAY_FAST = 10;
|
||||
protected static final int DELAY_SLOW = 100;
|
||||
protected static final int DELAY_LAUNCHER = DELAY_FAST * 2;
|
||||
|
||||
// 2 - 1 for the task itself; 1 for the launcher
|
||||
protected CountDownLatch threadsFinished = new CountDownLatch(2);
|
||||
protected Deque<TDEvent> eventQueue = new LinkedBlockingDeque<>();
|
||||
|
||||
protected volatile TaskLauncherSpy taskLauncherSpy;
|
||||
protected volatile TaskDialogSpy dialogSpy;
|
||||
protected AtomicBoolean didRunInBackground = new AtomicBoolean();
|
||||
|
||||
protected void assertDidNotRunInSwing() {
|
||||
for (TDEvent e : eventQueue) {
|
||||
assertFalse(e.getThreadName().contains("AWT"));
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertRanInSwingThread() {
|
||||
assertFalse("Task was not run in the Swing thread", didRunInBackground.get());
|
||||
}
|
||||
|
||||
protected void assertSwingThreadBlockedForTask() {
|
||||
TDEvent lastEvent = eventQueue.peekLast();
|
||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||
if (!swingIsLast) {
|
||||
fail("The Swing thread did not block until the task finished");
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertSwingThreadFinishedBeforeTask() {
|
||||
TDEvent lastEvent = eventQueue.peekLast();
|
||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||
if (swingIsLast) {
|
||||
fail("The Swing thread blocked until the task finished");
|
||||
}
|
||||
}
|
||||
|
||||
protected void waitForTask() throws Exception {
|
||||
threadsFinished.await(2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected void launchTask(Task task) {
|
||||
launchTaskFromSwing(task);
|
||||
}
|
||||
|
||||
protected void launchTaskFromSwing(Task task) {
|
||||
|
||||
runSwing(() -> {
|
||||
taskLauncherSpy = new TaskLauncherSpy(task);
|
||||
postEvent("After task launcher");
|
||||
threadsFinished.countDown();
|
||||
});
|
||||
}
|
||||
|
||||
protected void postEvent(String message) {
|
||||
eventQueue.add(new TDEvent(message));
|
||||
}
|
||||
|
||||
protected class TaskLauncherSpy extends TaskLauncher {
|
||||
|
||||
public TaskLauncherSpy(Task task) {
|
||||
super(task, null, DELAY_LAUNCHER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskRunner createTaskRunner(Task task, Component parent, int delay,
|
||||
int dialogWidth) {
|
||||
|
||||
return new TaskRunner(task, parent, delay, dialogWidth) {
|
||||
@Override
|
||||
protected TaskDialog buildTaskDialog(Component comp) {
|
||||
dialogSpy = new TaskDialogSpy(task);
|
||||
return dialogSpy;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSwingTimeoutInSeconds() {
|
||||
return 1; // speed-up for tests
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runInThisBackgroundThread(Task task) {
|
||||
didRunInBackground.set(true);
|
||||
super.runInThisBackgroundThread(task);
|
||||
}
|
||||
|
||||
TaskDialogSpy getDialogSpy() {
|
||||
return dialogSpy;
|
||||
}
|
||||
|
||||
boolean didRunInBackground() {
|
||||
return didRunInBackground.get();
|
||||
}
|
||||
}
|
||||
|
||||
protected class FastModalTask extends Task {
|
||||
|
||||
public FastModalTask() {
|
||||
super("Fast Modal Task", true, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(getName() + " started...");
|
||||
sleep(DELAY_FAST);
|
||||
threadsFinished.countDown();
|
||||
postEvent(getName() + " finished.");
|
||||
}
|
||||
}
|
||||
|
||||
protected class FastNonModalTask extends Task {
|
||||
|
||||
public FastNonModalTask() {
|
||||
super("Fast Non-modal Task", true, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(getName() + " started...");
|
||||
sleep(DELAY_FAST);
|
||||
postEvent(getName() + " finished.");
|
||||
threadsFinished.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
protected class SlowModalTask extends Task {
|
||||
|
||||
public SlowModalTask() {
|
||||
super("Slow Modal Task", true, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(getName() + " started...");
|
||||
sleep(DELAY_SLOW);
|
||||
threadsFinished.countDown();
|
||||
postEvent(getName() + " finished.");
|
||||
}
|
||||
}
|
||||
|
||||
protected class SlowNonModalTask extends Task {
|
||||
|
||||
public SlowNonModalTask() {
|
||||
super("Slow Non-modal Task", true, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(getName() + " started...");
|
||||
sleep(DELAY_SLOW);
|
||||
threadsFinished.countDown();
|
||||
postEvent(getName() + " finished.");
|
||||
}
|
||||
}
|
||||
|
||||
protected class TDEvent {
|
||||
|
||||
protected String threadName = Thread.currentThread().getName();
|
||||
protected String message;
|
||||
|
||||
TDEvent(String message) {
|
||||
this.message = message;
|
||||
|
||||
// Msg.out(message + " from " + threadName);
|
||||
}
|
||||
|
||||
String getThreadName() {
|
||||
return threadName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return message + " - thread [" + threadName + ']';
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class TaskDialogSpy extends TaskDialog {
|
||||
private AtomicBoolean shown = new AtomicBoolean();
|
||||
|
||||
public TaskDialogSpy(Task task) {
|
||||
super(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doShow() {
|
||||
shown.set(true);
|
||||
super.doShow();
|
||||
}
|
||||
|
||||
boolean wasShown() {
|
||||
return shown.get();
|
||||
}
|
||||
}
|
@ -17,26 +17,10 @@ package ghidra.util.task;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
|
||||
public class TaskDialogTest extends AbstractDockingTest {
|
||||
|
||||
private static final int DELAY_FAST = 10;
|
||||
private static final int DELAY_SLOW = 100;
|
||||
private static final int DELAY_LAUNCHER = DELAY_FAST * 2;
|
||||
|
||||
private CountDownLatch threadsFinished = new CountDownLatch(2);
|
||||
|
||||
private Deque<TDEvent> eventQueue = new LinkedBlockingDeque<>();
|
||||
public class TaskDialogTest extends AbstractTaskTest {
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
@ -48,7 +32,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||
|
||||
FastModalTask task = new FastModalTask();
|
||||
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
launchTask(task);
|
||||
|
||||
waitForTask();
|
||||
|
||||
@ -60,7 +44,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||
public void testModalDialog_SlowTask_Dialog() throws Exception {
|
||||
SlowModalTask task = new SlowModalTask();
|
||||
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
launchTask(task);
|
||||
|
||||
waitForTask();
|
||||
|
||||
@ -73,7 +57,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||
|
||||
FastNonModalTask task = new FastNonModalTask();
|
||||
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
launchTask(task);
|
||||
|
||||
waitForTask();
|
||||
|
||||
@ -86,7 +70,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||
|
||||
SlowNonModalTask task = new SlowNonModalTask();
|
||||
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
launchTask(task);
|
||||
|
||||
waitForTask();
|
||||
|
||||
@ -100,7 +84,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||
@Test
|
||||
public void testTaskCancel() throws Exception {
|
||||
SlowModalTask task = new SlowModalTask();
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
launchTask(task);
|
||||
|
||||
dialogSpy.doShow();
|
||||
|
||||
@ -118,7 +102,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||
@Test
|
||||
public void testTaskNoCancel() throws Exception {
|
||||
SlowModalTask task = new SlowModalTask();
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
launchTask(task);
|
||||
|
||||
dialogSpy.doShow();
|
||||
dialogSpy.setCancelEnabled(false);
|
||||
@ -128,159 +112,4 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||
assertFalse(dialogSpy.isCancelEnabled());
|
||||
}
|
||||
|
||||
private void assertSwingThreadBlockedForTask() {
|
||||
TDEvent lastEvent = eventQueue.peekLast();
|
||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||
if (!swingIsLast) {
|
||||
fail("The Swing thread did not block until the task finished");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSwingThreadFinishedBeforeTask() {
|
||||
TDEvent lastEvent = eventQueue.peekLast();
|
||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||
if (swingIsLast) {
|
||||
fail("The Swing thread blocked until the task finished");
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForTask() throws Exception {
|
||||
threadsFinished.await(2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private TaskDialogSpy launchTask(Task task) {
|
||||
AtomicReference<TaskDialogSpy> ref = new AtomicReference<>();
|
||||
runSwing(() -> {
|
||||
TaskLauncherSpy launcherSpy = new TaskLauncherSpy(task);
|
||||
postEvent("After task launcher");
|
||||
TaskDialogSpy dialogSpy = launcherSpy.getDialogSpy();
|
||||
ref.set(dialogSpy);
|
||||
threadsFinished.countDown();
|
||||
});
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
private void postEvent(String message) {
|
||||
eventQueue.add(new TDEvent(message));
|
||||
}
|
||||
|
||||
private class TaskLauncherSpy extends TaskLauncher {
|
||||
|
||||
private TaskDialogSpy dialogSpy;
|
||||
|
||||
public TaskLauncherSpy(Task task) {
|
||||
super(task, null, DELAY_LAUNCHER);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TaskDialog createTaskDialog(Component comp) {
|
||||
dialogSpy = new TaskDialogSpy(task);
|
||||
return dialogSpy;
|
||||
}
|
||||
|
||||
TaskDialogSpy getDialogSpy() {
|
||||
return dialogSpy;
|
||||
}
|
||||
}
|
||||
|
||||
private class TaskDialogSpy extends TaskDialog {
|
||||
|
||||
private AtomicBoolean shown = new AtomicBoolean();
|
||||
|
||||
public TaskDialogSpy(Task task) {
|
||||
super(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doShow() {
|
||||
shown.set(true);
|
||||
super.doShow();
|
||||
}
|
||||
|
||||
boolean wasShown() {
|
||||
return shown.get();
|
||||
}
|
||||
}
|
||||
|
||||
private class FastModalTask extends Task {
|
||||
|
||||
public FastModalTask() {
|
||||
super("Fast Modal Task", true, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(" started...");
|
||||
sleep(DELAY_FAST);
|
||||
threadsFinished.countDown();
|
||||
postEvent(" finished.");
|
||||
}
|
||||
}
|
||||
|
||||
private class FastNonModalTask extends Task {
|
||||
|
||||
public FastNonModalTask() {
|
||||
super("Fast Non-modal Task", true, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(" started...");
|
||||
sleep(DELAY_FAST);
|
||||
postEvent(" finished.");
|
||||
threadsFinished.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
private class SlowModalTask extends Task {
|
||||
|
||||
public SlowModalTask() {
|
||||
super("Slow Modal Task", true, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(" started...");
|
||||
sleep(DELAY_SLOW);
|
||||
threadsFinished.countDown();
|
||||
postEvent(" finished.");
|
||||
}
|
||||
}
|
||||
|
||||
private class SlowNonModalTask extends Task {
|
||||
|
||||
public SlowNonModalTask() {
|
||||
super("Slow Non-modal Task", true, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(" started...");
|
||||
sleep(DELAY_SLOW);
|
||||
threadsFinished.countDown();
|
||||
postEvent(" finished.");
|
||||
}
|
||||
}
|
||||
|
||||
private class TDEvent {
|
||||
|
||||
private String threadName = Thread.currentThread().getName();
|
||||
private String message;
|
||||
|
||||
TDEvent(String message) {
|
||||
this.message = message;
|
||||
|
||||
// Msg.out(message + " from " + threadName);
|
||||
}
|
||||
|
||||
String getThreadName() {
|
||||
return threadName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return message + " - thread [" + threadName + ']';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,177 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
public class TaskLauncherTest extends AbstractTaskTest {
|
||||
|
||||
private Thread swingThread;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
runSwing(() -> swingThread = Thread.currentThread());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// release any blockers
|
||||
swingThread.interrupt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchFromSwing() throws Exception {
|
||||
|
||||
FastModalTask task = new FastModalTask();
|
||||
launchTaskFromSwing(task);
|
||||
waitForTask();
|
||||
assertSwingThreadBlockedForTask();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchFromBackground() throws Exception {
|
||||
|
||||
FastModalTask task = new FastModalTask();
|
||||
launchTaskFromBackground(task);
|
||||
waitForTask();
|
||||
assertRanInSwingThread();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchFromBackgroundWithBusySwing() throws Exception {
|
||||
|
||||
SwingBlocker blocker = new SwingBlocker();
|
||||
runSwing(blocker, false);
|
||||
blocker.waitForStart();
|
||||
|
||||
FastModalTask task = new FastModalTask();
|
||||
launchTaskFromBackground(task);
|
||||
waitForTask();
|
||||
|
||||
assertDidNotRunInSwing();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchFromInsideOfAnotherTaskThread() throws Exception {
|
||||
|
||||
SwingBlocker blocker = new SwingBlocker();
|
||||
runSwing(blocker, false);
|
||||
blocker.waitForStart();
|
||||
|
||||
// 4 - 2 per task
|
||||
threadsFinished = new CountDownLatch(4);
|
||||
launchTaskFromTask();
|
||||
waitForTask();
|
||||
assertDidNotRunInSwing();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchFromInsideOfAnotherTaskThreadWithBusySwingThread() throws Exception {
|
||||
|
||||
// 4 - 2 per task
|
||||
threadsFinished = new CountDownLatch(4);
|
||||
launchTaskFromTask();
|
||||
waitForTask();
|
||||
assertDidNotRunInSwing();
|
||||
}
|
||||
|
||||
private int getWaitTimeoutInSeconds() {
|
||||
return (int) TimeUnit.SECONDS.convert(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS) * 2;
|
||||
}
|
||||
|
||||
protected void launchTaskFromBackground(Task task) throws InterruptedException {
|
||||
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
new Thread("Test Task Launcher Background Client") {
|
||||
@Override
|
||||
public void run() {
|
||||
taskLauncherSpy = new TaskLauncherSpy(task);
|
||||
start.countDown();
|
||||
postEvent("After task launcher");
|
||||
threadsFinished.countDown();
|
||||
}
|
||||
}.start();
|
||||
|
||||
assertTrue("Background thread did not start in " + getWaitTimeoutInSeconds() + " seconds",
|
||||
start.await(getWaitTimeoutInSeconds(), TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
protected void launchTaskFromTask() throws InterruptedException {
|
||||
|
||||
TaskLaunchingTask task = new TaskLaunchingTask();
|
||||
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
new Thread("Nested Test Task Launcher Background Client") {
|
||||
@Override
|
||||
public void run() {
|
||||
taskLauncherSpy = new TaskLauncherSpy(task);
|
||||
start.countDown();
|
||||
postEvent("After task launcher");
|
||||
threadsFinished.countDown();
|
||||
}
|
||||
}.start();
|
||||
|
||||
assertTrue("Background thread did not start in " + getWaitTimeoutInSeconds() + " seconds",
|
||||
start.await(getWaitTimeoutInSeconds(), TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
private class TaskLaunchingTask extends Task {
|
||||
|
||||
public TaskLaunchingTask() {
|
||||
super("Slow Modal Task", true, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
postEvent(getName() + " started...");
|
||||
|
||||
sleep(DELAY_FAST);
|
||||
try {
|
||||
launchTaskFromBackground(new FastModalTask());
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
threadsFinished.countDown();
|
||||
postEvent(getName() + " finished.");
|
||||
}
|
||||
}
|
||||
|
||||
private class SwingBlocker implements Runnable {
|
||||
|
||||
private static final long REALLY_LONG_SLEEP_THAT_DOESNT_FINISH_MS = 20000;
|
||||
|
||||
private CountDownLatch started = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
started.countDown();
|
||||
sleep(REALLY_LONG_SLEEP_THAT_DOESNT_FINISH_MS);
|
||||
}
|
||||
|
||||
void waitForStart() throws InterruptedException {
|
||||
assertTrue("Swing blocker did not start",
|
||||
started.await(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -174,7 +174,7 @@ public class GThreadPool {
|
||||
*
|
||||
* @return the executor
|
||||
*/
|
||||
public GThreadPoolExecutor getExecutor() {
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ public class TaskUtilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener added via {@link #addTrackedTask(Task)}.
|
||||
* Removes the given listener added via {@link #addTrackedTask(Task,TaskMonitor)}.
|
||||
* @param listener The listener that needs to be removed.
|
||||
*/
|
||||
public static void removeTrackedTaskListener(TrackedTaskListener listener) {
|
||||
|
@ -19,12 +19,12 @@ import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.TimeoutException;
|
||||
import ghidra.util.timer.GTimer;
|
||||
import ghidra.util.timer.GTimerMonitor;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* A task monitor that allows clients the ability to specify a timeout after which this monitor
|
||||
|
@ -17,6 +17,7 @@ package ghidra.util.worker;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@ -27,8 +28,6 @@ import ghidra.util.task.TaskMonitor;
|
||||
*/
|
||||
public class Worker extends AbstractWorker<Job> {
|
||||
|
||||
public static final String GSWING_THREAD_POOL_NAME = "GSwing Worker";
|
||||
|
||||
/**
|
||||
* A convenience method to create a Worker that uses a shared thread pool for performing
|
||||
* operations for GUI clients in a background thread
|
||||
@ -42,7 +41,7 @@ public class Worker extends AbstractWorker<Job> {
|
||||
* @return the new worker
|
||||
*/
|
||||
public static Worker createGuiWorker() {
|
||||
return new Worker(GSWING_THREAD_POOL_NAME);
|
||||
return new Worker(Swing.GSWING_THREAD_POOL_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,12 +24,12 @@ import com.google.common.collect.Iterators;
|
||||
|
||||
import edu.uci.ics.jung.graph.util.EdgeType;
|
||||
import edu.uci.ics.jung.graph.util.Pair;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.graph.GraphAlgorithms;
|
||||
import ghidra.graph.viewer.VisualEdge;
|
||||
import ghidra.graph.viewer.VisualVertex;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import util.CollectionUtils;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* A graph implementation that allows clients to mark vertices and edges as filtered. When
|
||||
|
@ -17,11 +17,11 @@ package ghidra.graph.job;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.QueueStub;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* A class to run {@link GraphJob}s. This class will queue jobs and will run them
|
||||
|
@ -25,7 +25,6 @@ import java.util.Objects;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import edu.uci.ics.jung.visualization.*;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.job.*;
|
||||
import ghidra.graph.viewer.edge.routing.BasicEdgeRouter;
|
||||
@ -34,6 +33,7 @@ import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.task.BusyListener;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* This is the class through which operations travel that manipulate the view and graph <b>while
|
||||
|
@ -23,7 +23,6 @@ import java.util.function.Supplier;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import generic.concurrent.GThreadPool;
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.graph.*;
|
||||
import ghidra.graph.algo.ChkDominanceAlgorithm;
|
||||
import ghidra.graph.algo.ChkPostDominanceAlgorithm;
|
||||
@ -34,6 +33,7 @@ import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.CallbackAccumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* A class that calculates flow between vertices and then triggers that flow to be painted
|
||||
|
@ -18,10 +18,10 @@ package ghidra.graph.algo.viewer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
import utility.function.Callback;
|
||||
|
||||
/**
|
||||
* Task monitor that will trigger a {@link #wait()} when {@link #checkCanceled()} is called. This
|
||||
|
@ -82,10 +82,6 @@ class PluginManager {
|
||||
return set.toArray(cl);
|
||||
}
|
||||
|
||||
void addPlugin(String className) throws PluginException {
|
||||
addPlugins(new String[] { className });
|
||||
}
|
||||
|
||||
void addPlugin(Plugin plugin) throws PluginException {
|
||||
addPlugins(new Plugin[] { plugin });
|
||||
}
|
||||
@ -369,12 +365,6 @@ class PluginManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the data state for all plugins in the tool to an XML element.
|
||||
* @param isTransactionState true if saving the toolstate is for a potential undo/redo
|
||||
* (database transaction)
|
||||
* @return XML element containing data state for all plugins.
|
||||
*/
|
||||
Element saveDataStateToXml(boolean savingProject) {
|
||||
Element root = new Element("DATA_STATE");
|
||||
for (int i = 0; i < pluginList.size(); i++) {
|
||||
|
279
Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java
Normal file
279
Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java
Normal file
@ -0,0 +1,279 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.UnableToSwingException;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
/**
|
||||
* A utility class to handle running code on the AWT Event Dispatch Thread
|
||||
*/
|
||||
public class Swing {
|
||||
|
||||
private static final String SWING_TIMEOUT_SECONDS_PROPERTY =
|
||||
Swing.class.getName().toLowerCase() + ".timeout.seconds";
|
||||
private static final int SWING_TIMEOUT_SECONDS_DEFAULT_VALUE = 10;
|
||||
|
||||
private static int loadTimeout() {
|
||||
String timeoutString = System.getProperty(SWING_TIMEOUT_SECONDS_PROPERTY,
|
||||
Integer.toString(SWING_TIMEOUT_SECONDS_DEFAULT_VALUE));
|
||||
|
||||
try {
|
||||
return Integer.parseInt(timeoutString);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return SWING_TIMEOUT_SECONDS_DEFAULT_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int SWING_TIMEOUT_SECONDS_VALUE = loadTimeout();
|
||||
|
||||
private static final String SWING_RUN_ERROR_MSG =
|
||||
"Unexpected exception running a task in the Swing Thread: ";
|
||||
|
||||
public static final String GSWING_THREAD_POOL_NAME = "GSwing Worker";
|
||||
|
||||
/**
|
||||
* Returns true if this is the event dispatch thread. Note that this method returns true in
|
||||
* headless mode because any thread in headless mode can dispatch its own events. In swing
|
||||
* environments, the swing thread is usually used to dispatch events.
|
||||
*
|
||||
* @return true if this is the event dispatch thread -OR- is in headless mode.
|
||||
*/
|
||||
public static boolean isEventDispatchThread() {
|
||||
if (isInHeadlessMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: just calling this method may trigger the AWT thread to get created
|
||||
return SwingUtilities.isEventDispatchThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until AWT event queue (Swing) has been flushed and no more (to a point) events
|
||||
* are pending.
|
||||
*/
|
||||
public static void allowSwingToProcessEvents() {
|
||||
Runnable r = () -> {
|
||||
// do nothing...this is just a placeholder runnable that gets put onto the stack
|
||||
};
|
||||
runNow(r);
|
||||
runNow(r);
|
||||
runNow(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* A development/testing time method to make sure the current thread is the swing thread.
|
||||
* @param errorMessage The message to display when the assert fails
|
||||
*/
|
||||
public static void assertThisIsTheSwingThread(String errorMessage) {
|
||||
boolean isProductionMode =
|
||||
!SystemUtilities.isInTestingMode() && !SystemUtilities.isInDevelopmentMode();
|
||||
if (isProductionMode) {
|
||||
return; // squash during production mode
|
||||
}
|
||||
|
||||
if (!isEventDispatchThread()) {
|
||||
Throwable t =
|
||||
ReflectionUtilities.filterJavaThrowable(new AssertException(errorMessage));
|
||||
Msg.error(SystemUtilities.class, errorMessage, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given suppler on the Swing thread, blocking with a
|
||||
* {@link SwingUtilities#invokeAndWait(Runnable)} if not on the Swing thread.
|
||||
*
|
||||
* <p>Use this method when you are not on the Swing thread and you need to get a value
|
||||
* that is managed/synchronized by the Swing thread.
|
||||
*
|
||||
* <pre>
|
||||
* String value = runNow(() -> label.getText());
|
||||
* </pre>
|
||||
*
|
||||
* @param s the supplier that will be called on the Swing thread
|
||||
* @return the result of the supplier
|
||||
* @see #runNow(Runnable)
|
||||
*/
|
||||
public static <T> T runNow(Supplier<T> s) {
|
||||
AtomicReference<T> ref = new AtomicReference<>();
|
||||
runNow(() -> ref.set(s.get()));
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given runnable on the Swing thread
|
||||
*
|
||||
* @param r the runnable
|
||||
* @see #runNow(Supplier) if you need to return a value from the Swing thread.
|
||||
*/
|
||||
public static void runNow(Runnable r) {
|
||||
|
||||
try {
|
||||
// not sure what a reasonable wait is for a background thread; we can make this larger
|
||||
// if we find that a really slow system UI causes this to fail
|
||||
runNow(r, SWING_TIMEOUT_SECONDS_VALUE, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (UnableToSwingException e) {
|
||||
throw new RuntimeException("Timed-out waiting to run a Swing task--potential deadlock!",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given runnable on the Swing thread
|
||||
*
|
||||
* <p>This method will throw an exception if the Swing thread is not available within the
|
||||
* given timeout. This method is useful for preventing deadlocks.
|
||||
*
|
||||
* @param r the runnable
|
||||
* @param timeout the timeout value
|
||||
* @param unit the time unit of the timeout value
|
||||
* @throws UnableToSwingException if the timeout was reach waiting for the Swing thread
|
||||
* @see #runNow(Supplier) if you need to return a value from the Swing thread.
|
||||
*/
|
||||
public static void runNow(Runnable r, long timeout, TimeUnit unit)
|
||||
throws UnableToSwingException {
|
||||
|
||||
if (isInHeadlessMode() || SystemUtilities.isEventDispatchThread()) {
|
||||
doRun(r, true, SWING_RUN_ERROR_MSG);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
We use the CyclicBarrier to force this thread and the Swing thread to wait for each
|
||||
other. This allows the calling thread to know if/when the Swing thread starts and
|
||||
the Swing thread to know if the calling thread timed-out.
|
||||
*/
|
||||
CyclicBarrier start = new CyclicBarrier(2);
|
||||
CyclicBarrier end = new CyclicBarrier(2);
|
||||
|
||||
runLater(() -> {
|
||||
|
||||
if (!waitFor(start)) {
|
||||
return; // must have timed-out
|
||||
}
|
||||
|
||||
try {
|
||||
r.run();
|
||||
}
|
||||
finally {
|
||||
waitFor(end);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (!waitFor(start, timeout, unit)) {
|
||||
throw new UnableToSwingException(
|
||||
"Timed-out waiting for Swing thread lock in " + timeout + " " + unit);
|
||||
}
|
||||
|
||||
waitFor(end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given runnable on the Swing thread in the future by putting the request on
|
||||
* the back of the event queue.
|
||||
*
|
||||
* @param r the runnable
|
||||
*/
|
||||
public static void runLater(Runnable r) {
|
||||
doRun(r, false, SWING_RUN_ERROR_MSG);
|
||||
}
|
||||
|
||||
public static void runIfSwingOrRunLater(Runnable r) {
|
||||
if (isInHeadlessMode()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
r.run();
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(r);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean waitFor(CyclicBarrier barrier, long timeout, TimeUnit unit) {
|
||||
|
||||
try {
|
||||
barrier.await(timeout, unit);
|
||||
return true;
|
||||
}
|
||||
catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
|
||||
// our Swing tasks may be interrupted from the framework
|
||||
}
|
||||
|
||||
// timed-out or was interrupted
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean waitFor(CyclicBarrier barrier) {
|
||||
|
||||
try {
|
||||
barrier.await();
|
||||
return true;
|
||||
}
|
||||
catch (InterruptedException | BrokenBarrierException e) {
|
||||
// our Swing tasks may be interrupted from the framework
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isInHeadlessMode() {
|
||||
return SystemUtilities.isInHeadlessMode();
|
||||
}
|
||||
|
||||
private static void doRun(Runnable r, boolean wait, String errorMessage) {
|
||||
if (isInHeadlessMode()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wait) {
|
||||
SwingUtilities.invokeLater(r);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(r);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// we sometimes interrupt our tasks intentionally, so don't report it
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Msg.error(Swing.class, errorMessage + "\nException Message: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Swing() {
|
||||
// utility class
|
||||
}
|
||||
}
|
@ -17,12 +17,10 @@ package ghidra.util;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
@ -37,9 +35,6 @@ import utilities.util.reflection.ReflectionUtilities;
|
||||
*/
|
||||
public class SystemUtilities {
|
||||
|
||||
private static final String SWING_RUN_ERROR_MSG =
|
||||
"Unexpected exception running a task in the Swing Thread: ";
|
||||
|
||||
private final static String DATE_TIME_FORMAT = "MMM d yyyy HH:mm:ss";
|
||||
|
||||
private static String userName;
|
||||
@ -245,9 +240,7 @@ public class SystemUtilities {
|
||||
* @see #runSwingNow(Runnable)
|
||||
*/
|
||||
public static <T> T runSwingNow(Supplier<T> s) {
|
||||
AtomicReference<T> ref = new AtomicReference<>();
|
||||
runSwingNow(() -> ref.set(s.get()));
|
||||
return ref.get();
|
||||
return Swing.runNow(s);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,7 +250,7 @@ public class SystemUtilities {
|
||||
* @see #runSwingNow(Supplier) if you need to return a value from the Swing thread.
|
||||
*/
|
||||
public static void runSwingNow(Runnable r) {
|
||||
runSwing(r, true, SWING_RUN_ERROR_MSG);
|
||||
Swing.runNow(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,48 +260,11 @@ public class SystemUtilities {
|
||||
* @param r the runnable
|
||||
*/
|
||||
public static void runSwingLater(Runnable r) {
|
||||
runSwing(r, false, SWING_RUN_ERROR_MSG);
|
||||
Swing.runLater(r);
|
||||
}
|
||||
|
||||
public static void runIfSwingOrPostSwingLater(Runnable r) {
|
||||
if (isInHeadlessMode()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
r.run();
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(r);
|
||||
}
|
||||
}
|
||||
|
||||
private static void runSwing(Runnable r, boolean wait, String errorMessage) {
|
||||
if (isInHeadlessMode()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(r);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// we sometimes interrupt our tasks intentionally, so don't report it
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Msg.error(SystemUtilities.class,
|
||||
errorMessage + "\nException Message: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(r);
|
||||
}
|
||||
Swing.runIfSwingOrRunLater(r);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -466,25 +422,7 @@ public class SystemUtilities {
|
||||
* @return true if this is the event dispatch thread -OR- is in headless mode.
|
||||
*/
|
||||
public static boolean isEventDispatchThread() {
|
||||
if (isInHeadlessMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: just calling this method may trigger the AWT thread to get created
|
||||
return SwingUtilities.isEventDispatchThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until AWT event queue (Swing) has been flushed and no more (to a point) events
|
||||
* are pending.
|
||||
*/
|
||||
public static void allowSwingToProcessEvents() {
|
||||
Runnable r = () -> {
|
||||
// do nothing...this is just a placeholder runnable that gets put onto the stack
|
||||
};
|
||||
runSwingNow(r);
|
||||
runSwingNow(r);
|
||||
runSwingNow(r);
|
||||
return Swing.isEventDispatchThread();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,30 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.exception;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* Signals that a background thread attempted to {@link SwingUtilities#invokeAndWait(Runnable)}
|
||||
* operation that timed-out because the Swing thread was busy. This can be a sign of
|
||||
* a deadlock.
|
||||
*/
|
||||
public class UnableToSwingException extends Exception {
|
||||
|
||||
public UnableToSwingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.generic.function;
|
||||
package utility.function;
|
||||
|
||||
/**
|
||||
* A generic functional interface that is more semantically sound than {@link Runnable}. Use
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.generic.function;
|
||||
package utility.function;
|
||||
|
||||
/**
|
||||
* A generic functional interface that is more semantically sound than {@link Runnable}. Use
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.generic.function;
|
||||
package utility.function;
|
||||
|
||||
/**
|
||||
* A generic functional interface that allows you to consume an item and potentially throw
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.generic.function;
|
||||
package utility.function;
|
||||
|
||||
/**
|
||||
* A generic functional interface that allows you to consume an item, return a result,
|
Loading…
Reference in New Issue
Block a user