GT-3567 - Fixed keybindings not working in DialogComponentProviders

This commit is contained in:
dragonmacher 2020-03-06 12:15:26 -05:00
parent 7a85fdac25
commit 314100a70c
33 changed files with 922 additions and 362 deletions

View file

@ -15,12 +15,9 @@
*/
package ghidra.app.plugin.core.functionwindow;
import javax.swing.KeyStroke;
import docking.ComponentProvider;
import docking.ComponentProviderActivationListener;
import docking.action.DockingAction;
import docking.action.KeyBindingData;
import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
@ -29,8 +26,6 @@ import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction;
import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.model.*;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
@ -55,7 +50,7 @@ import ghidra.util.task.SwingUpdateManager;
)
//@formatter:on
public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectListener,
OptionsChangeListener, ComponentProviderActivationListener {
ComponentProviderActivationListener {
private DockingAction selectAction;
private DockingAction compareFunctionsAction;
@ -66,12 +61,7 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
public FunctionWindowPlugin(PluginTool tool) {
super(tool, true, false);
swingMgr = new SwingUpdateManager(1000, new Runnable() {
@Override
public void run() {
provider.reload();
}
});
swingMgr = new SwingUpdateManager(1000, () -> provider.reload());
}
@Override
@ -221,20 +211,6 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL
tool.addLocalAction(provider, compareFunctionsAction);
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
if (optionName.startsWith(selectAction.getName())) {
KeyStroke keyStroke = (KeyStroke) newValue;
selectAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke));
}
if (optionName.startsWith(compareFunctionsAction.getName())) {
KeyStroke keyStroke = (KeyStroke) newValue;
compareFunctionsAction.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke));
}
}
void showFunctions() {
provider.showFunctions();
}

View file

@ -51,6 +51,8 @@ public class MakeProgramSelectionAction extends DockingAction {
*/
public MakeProgramSelectionAction(String owner, GhidraTable table) {
super("Make Selection", owner, KeyBindingType.SHARED);
this.table = table;
init();
}
/**
@ -65,6 +67,10 @@ public class MakeProgramSelectionAction extends DockingAction {
this.plugin = plugin;
this.table = table;
init();
}
private void init() {
setPopupMenuData(
new MenuData(new String[] { "Make Selection" }, Icons.MAKE_SELECTION_ICON));
setToolBarData(new ToolBarData(Icons.MAKE_SELECTION_ICON));

View file

@ -169,7 +169,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
setKeyBindingViaF4Dialog_FromWindowsMenu(newKs);
hideProvider();
pressKey(CONTROL_T);
triggerKey(tool.getToolFrame(), CONTROL_T);
assertProviderIsActive();
}
@ -337,7 +337,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
// Note: there may be a test focus issue here. If this test fails sporadically due to
// how the action context is generated (it depends on focus). It is only useful to fail
// here in development mode.
pressKey(controlEsc);
triggerKey(tool.getToolFrame(), controlEsc);
assertProviderIsHidden_InNonBatchMode();
}
@ -398,14 +398,6 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
});
}
private void pressKey(KeyStroke ks) {
int modifiers = ks.getModifiers();
char keyChar = ks.getKeyChar();
int keyCode = ks.getKeyCode();
JFrame toolFrame = tool.getToolFrame();
triggerKey(toolFrame, modifiers, keyCode, keyChar);
}
private DockingActionIf getShowProviderAction() {
DockingActionIf showProviderAction =

View file

@ -15,31 +15,33 @@
*/
package ghidra.app.tablechooser;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.swing.*;
import org.junit.*;
import docking.*;
import docking.action.*;
import docking.actions.KeyEntryDialog;
import docking.actions.ToolActions;
import docking.tool.util.DockingToolConstants;
import ghidra.app.nav.Navigatable;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.DummyPluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.TestAddress;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.ToyProgramBuilder;
import resources.Icons;
import util.CollectionUtils;
public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest {
@ -48,8 +50,9 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
private static final TestExecutorDecision DEFAULT_DECISION = r -> true;
private DummyPluginTool tool;
private TableChooserDialog dialog;
private SpyTableChooserExecutor executor;
private TableChooserDialog dialog;
private TestAction testAction;
/** Interface for tests to signal what is expected of the executor */
private TestExecutorDecision testDecision = DEFAULT_DECISION;
@ -64,7 +67,6 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
public void tearDown() {
runSwing(() -> {
tool.close();
//dialog.close();
});
}
@ -76,6 +78,10 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
Program program = new ToyProgramBuilder("Test", true).getProgram();
Navigatable navigatable = null;
dialog = new TableChooserDialog(tool, executor, program, "Title", navigatable);
testAction = new TestAction();
dialog.addAction(testAction);
dialog.show();
loadData();
}
@ -250,10 +256,104 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
assertOnlyExecutedOnce(selected2);
}
@Test
public void testActionToolBarButtonIconUpdate() {
Icon icon = testAction.getToolBarData().getIcon();
JButton button = getToolBarButton(icon);
assertNotNull("Could not find button for icon: " + icon, button);
Icon newIcon = Icons.LEFT_ICON;
runSwing(() -> testAction.setToolBarData(new ToolBarData(newIcon)));
button = getToolBarButton(newIcon);
assertNotNull("Could not find button for icon: " + icon, button);
}
@Test
public void testActionKeyBinding() {
KeyStroke ks = testAction.getKeyBinding();
triggerKey(dialog.getComponent(), ks);
assertTrue(testAction.wasInvoked());
}
@Test
public void testActionKeyBinding_ChangeKeyBinding_FromOptions() {
KeyStroke newKs = KeyStroke.getKeyStroke('A', 0, false);
setOptionsKeyStroke(testAction, newKs);
triggerKey(dialog.getComponent(), newKs);
assertTrue(testAction.wasInvoked());
}
@Test
public void testActionKeyBinding_ChangeKeyBinding_FromKeyBindingDialog() {
KeyStroke newKs = KeyStroke.getKeyStroke('A', 0, false);
setKeyBindingViaF4Dialog(testAction, newKs);
triggerKey(dialog.getComponent(), newKs);
assertTrue(testAction.wasInvoked());
}
@Test
public void testSetKeyBindingUpdatesToolBarButtonTooltip() {
JButton button = getToolBarButton(testAction);
String toolTip = button.getToolTipText();
assertTrue(toolTip.contains("(Z)"));
KeyStroke newKs = KeyStroke.getKeyStroke('A', 0, false);
setOptionsKeyStroke(testAction, newKs);
String newToolTip = button.getToolTipText();
assertTrue(newToolTip.contains("(A)"));
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void setKeyBindingViaF4Dialog(DockingAction action, KeyStroke ks) {
// simulate the user mousing over the toolbar button
assertNotNull("Provider action not installed in toolbar", action);
DockingWindowManager.setMouseOverAction(action);
performLaunchKeyStrokeDialogAction();
KeyEntryDialog keyDialog = waitForDialogComponent(KeyEntryDialog.class);
runSwing(() -> keyDialog.setKeyStroke(ks));
pressButtonByText(keyDialog, "OK");
assertFalse("Invalid key stroke: " + ks, runSwing(() -> keyDialog.isVisible()));
}
private void performLaunchKeyStrokeDialogAction() {
ToolActions toolActions = (ToolActions) ((AbstractDockingTool) tool).getToolActions();
Action action = toolActions.getAction(KeyStroke.getKeyStroke("F4"));
assertNotNull(action);
runSwing(() -> action.actionPerformed(new ActionEvent(this, 0, "")), false);
}
private void setOptionsKeyStroke(DockingAction action, KeyStroke newKs) {
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
String name = action.getName() + " (" + action.getOwner() + ")";
runSwing(() -> keyOptions.setKeyStroke(name, newKs));
waitForSwing();
KeyStroke actual = action.getKeyBinding();
assertEquals("Key binding was not updated after changing options", newKs, actual);
}
private JButton getToolBarButton(TestAction action) {
return getToolBarButton(action.getToolBarData().getIcon());
}
private JButton getToolBarButton(Icon icon) {
JButton button = findButtonByIcon(dialog.getComponent(), icon);
return button;
}
private void assertRowCount(int expected) {
int actual = getRowCount();
assertEquals("Table model row count is not as expected", expected, actual);
@ -400,4 +500,29 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
return getAddress().toString();
}
}
private class TestAction extends DockingAction {
private int invoked;
TestAction() {
super("Test Action", "Test Owner");
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0, false);
setKeyBindingData(new KeyBindingData(ks));
setToolBarData(new ToolBarData(Icons.ERROR_ICON));
}
@Override
public void actionPerformed(ActionContext context) {
invoked++;
}
boolean wasInvoked() {
if (invoked > 1) {
fail("Action invoked more than once");
}
return invoked == 1;
}
}
}

View file

@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.*;
import java.util.*;
@ -152,6 +153,33 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
debug.close();
}
@Test
public void testParseKeyStroke() {
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_V, 0);
String parsed = KeyBindingUtils.parseKeyStroke(ks);
assertEquals("V", parsed);
ks = KeyStroke.getKeyStroke('v');
parsed = KeyBindingUtils.parseKeyStroke(ks);
assertEquals("v", parsed);
int modifiers = InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK;
ks = KeyStroke.getKeyStroke(KeyEvent.VK_V, modifiers);
parsed = KeyBindingUtils.parseKeyStroke(ks);
assertEquals("Ctrl-Shift-V", parsed);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_V, modifiers, true);
parsed = KeyBindingUtils.parseKeyStroke(ks);
assertEquals("Ctrl-Shift-V", parsed);
JButton b = new JButton();
KeyEvent event = new KeyEvent(b, KeyEvent.KEY_PRESSED, 1, modifiers, KeyEvent.VK_V, 'v');
ks = KeyStroke.getKeyStrokeForEvent(event);
parsed = KeyBindingUtils.parseKeyStroke(ks);
assertEquals("Ctrl-Shift-V", parsed);
}
/*
* Test method for 'ghidra.framework.plugintool.dialog.KeyBindingUtils.importKeyBindings(PluginTool)'
*/
@ -364,7 +392,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
private void reopenTool(PluginTool tool2) {
runSwing(() -> {
ToolServices services = tool.getProject().getToolServices();
tool = (PluginTool) services.launchTool(tool.getName(), null);
tool = services.launchTool(tool.getName(), null);
});
assertNotNull(tool);
}

View file

@ -23,7 +23,8 @@
// @category Analysis
import java.util.*;
import java.util.Iterator;
import java.util.function.Consumer;
import org.apache.commons.collections4.IteratorUtils;
@ -33,6 +34,8 @@ import ghidra.app.script.GhidraScript;
import ghidra.app.tablechooser.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.PcodeOpAST;
import ghidra.util.task.TaskMonitor;
public class CompareFunctionSizesScript extends GhidraScript {
@ -41,51 +44,44 @@ public class CompareFunctionSizesScript extends GhidraScript {
protected void run() throws Exception {
if (isRunningHeadless()) {
printf("This script cannot be run headlessly.\n");
println("This script cannot be run headlessly");
return;
}
DecompilerCallback<FuncBodyData> callback = new DecompilerCallback<FuncBodyData>(
TableChooserDialog tableDialog =
createTableChooserDialog(currentProgram.getName() + " function sizes", null);
configureTableColumns(tableDialog);
tableDialog.show();
DecompilerCallback<FuncBodyData> callback = new DecompilerCallback<>(
currentProgram, new CompareFunctionSizesScriptConfigurer(currentProgram)) {
@Override
public FuncBodyData process(DecompileResults results, TaskMonitor tMonitor)
throws Exception {
InstructionIterator instIter = currentProgram.getListing().getInstructions(
results.getFunction().getBody(), true);
int numInstructions = IteratorUtils.size(instIter);
//indicate failure of decompilation by having 0 high pcode ops
Listing listing = currentProgram.getListing();
Function function = results.getFunction();
InstructionIterator it = listing.getInstructions(function.getBody(), true);
int numInstructions = IteratorUtils.size(it);
// indicate failure of decompilation by having 0 high pcode ops
int numHighOps = 0;
if (results.getHighFunction() != null &&
results.getHighFunction().getPcodeOps() != null) {
numHighOps = IteratorUtils.size(results.getHighFunction().getPcodeOps());
HighFunction highFunction = results.getHighFunction();
if (highFunction != null) {
Iterator<PcodeOpAST> ops = highFunction.getPcodeOps();
if (ops != null) {
numHighOps = IteratorUtils.size(ops);
}
}
return new FuncBodyData(results.getFunction(), numInstructions, numHighOps);
return new FuncBodyData(function, numInstructions, numHighOps);
}
};
Set<Function> funcsToDecompile = new HashSet<>();
FunctionIterator fIter = currentProgram.getFunctionManager().getFunctionsNoStubs(true);
fIter.forEach(e -> funcsToDecompile.add(e));
if (funcsToDecompile.isEmpty()) {
popup("No functions to decompile!");
return;
}
List<FuncBodyData> funcBodyData = ParallelDecompiler.decompileFunctions(callback,
currentProgram, funcsToDecompile, monitor);
monitor.checkCanceled();
TableChooserDialog tableDialog =
createTableChooserDialog(currentProgram.getName() + " function sizes", null);
configureTableColumns(tableDialog);
tableDialog.show();
for (FuncBodyData bodyData : funcBodyData) {
tableDialog.add(bodyData);
}
Consumer<FuncBodyData> consumer = data -> tableDialog.add(data);
FunctionIterator it = currentProgram.getFunctionManager().getFunctionsNoStubs(true);
ParallelDecompiler.decompileFunctions(callback, currentProgram, it, consumer, monitor);
callback.dispose();
}
class CompareFunctionSizesScriptConfigurer implements DecompileConfigurer {
@ -210,7 +206,7 @@ public class CompareFunctionSizesScript extends GhidraScript {
}
};
ColumnDisplay<Integer> highOpsColumn = new AbstractComparableColumnDisplay<Integer>() {
ColumnDisplay<Integer> highOpsColumn = new AbstractComparableColumnDisplay<>() {
@Override
public Integer getColumnValue(AddressableRowObject rowObject) {
@ -223,7 +219,7 @@ public class CompareFunctionSizesScript extends GhidraScript {
}
};
ColumnDisplay<Integer> instructionColumn = new AbstractComparableColumnDisplay<Integer>() {
ColumnDisplay<Integer> instructionColumn = new AbstractComparableColumnDisplay<>() {
@Override
public Integer getColumnValue(AddressableRowObject rowObject) {
@ -236,7 +232,7 @@ public class CompareFunctionSizesScript extends GhidraScript {
}
};
ColumnDisplay<Double> ratioColumn = new AbstractComparableColumnDisplay<Double>() {
ColumnDisplay<Double> ratioColumn = new AbstractComparableColumnDisplay<>() {
@Override
public Double getColumnValue(AddressableRowObject rowObject) {

View file

@ -76,7 +76,7 @@ public class FindPotentialDecompilerProblems extends GhidraScript {
return;
}
ParallelDecompiler.decompileFunctions(callback, currentProgram, funcsToDecompile, monitor);
ParallelDecompiler.decompileFunctions(callback, funcsToDecompile, monitor);
monitor.checkCanceled();
tableDialog.setMessage("Finished");
}

View file

@ -60,7 +60,7 @@ public class FixSwitchStatementsWithDecompiler extends GhidraScript {
Set<Function> functions = instructionsByFunction.keySet();
try {
ParallelDecompiler.decompileFunctions(callback, currentProgram, functions, monitor);
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
}
finally {
callback.dispose();

View file

@ -16,6 +16,7 @@
package ghidra.app.decompiler.parallel;
import java.util.*;
import java.util.function.Consumer;
import generic.concurrent.QCallback;
import generic.concurrent.QResult;
@ -46,7 +47,7 @@ public class ParallelDecompiler {
Listing listing = program.getListing();
FunctionIterator iterator = listing.getFunctions(addresses, true);
List<R> results = decompileFunctions(callback, program, iterator, functionCount, monitor);
List<R> results = doDecompileFunctions(callback, iterator, functionCount, monitor);
return results;
}
@ -54,23 +55,54 @@ public class ParallelDecompiler {
* Decompile the given functions using multiple decompilers
*
* @param callback the callback to be called for each that is processed
* @param program the program
* @param functions the functions to decompile
* @param monitor the task monitor
* @return the list of client results
* @throws InterruptedException if interrupted
* @throws Exception if any other exception occurs
*/
public static <R> List<R> decompileFunctions(QCallback<Function, R> callback, Program program,
Collection<Function> functions, TaskMonitor monitor)
public static <R> List<R> decompileFunctions(QCallback<Function, R> callback,
Collection<Function> functions,
TaskMonitor monitor)
throws InterruptedException, Exception {
List<R> results =
decompileFunctions(callback, program, functions.iterator(), functions.size(), monitor);
doDecompileFunctions(callback, functions.iterator(), functions.size(),
monitor);
return results;
}
private static <R> List<R> decompileFunctions(QCallback<Function, R> callback, Program program,
/**
* Decompile the given functions using multiple decompilers.
*
* <p>Results will be passed to the given consumer as they are produced. Calling this
* method allows you to handle results as they are discovered.
*
* <p><strong>This method will wait for all processing before returning.</strong>
*
* @param callback the callback to be called for each that is processed
* @param program the program
* @param functions the functions to decompile
* @param resultsConsumer the consumer to which results will be passed
* @param monitor the task monitor
* @throws InterruptedException if interrupted
* @throws Exception if any other exception occurs
*/
public static <R> void decompileFunctions(QCallback<Function, R> callback,
Program program,
Iterator<Function> functions, Consumer<R> resultsConsumer,
TaskMonitor monitor)
throws InterruptedException, Exception {
int max = program.getFunctionManager().getFunctionCount();
DecompilerConcurrentQ<Function, R> queue =
new DecompilerConcurrentQ<>(callback, THREAD_POOL_NAME, monitor);
monitor.initialize(max);
queue.process(functions, resultsConsumer);
queue.waitUntilDone();
}
private static <R> List<R> doDecompileFunctions(QCallback<Function, R> callback,
Iterator<Function> functions, int count, TaskMonitor monitor)
throws InterruptedException, Exception {

View file

@ -162,7 +162,7 @@ public class DecompilerSwitchAnalyzer extends AbstractAnalyzer {
callback.setTimeout(decompilerTimeoutSecondsOption);
try {
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
}
finally {
callback.dispose();

View file

@ -108,7 +108,7 @@ public class ObjectiveC2_DecompilerMessageAnalyzer extends AbstractAnalyzer {
};
try {
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
}
finally {
callback.dispose();

View file

@ -62,7 +62,7 @@ public class DecompilerValidator extends PostAnalysisValidator {
try {
List<String> results =
ParallelDecompiler.decompileFunctions(callback, program, functions, monitor);
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
return processResults(results);
}
catch (Exception e) {

View file

@ -15,26 +15,39 @@
*/
package ghidra.app.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import generic.concurrent.*;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
import utility.function.Dummy;
/**
* A class to perform some of the boilerplate setup of the {@link ConcurrentQ} that is shared
* amongst clients that perform decompilation in parallel.
*
* <p>This class can be used in a blocking or non-blocking fashion.
*
* <ul>
* <li>For blocking usage, call
* one of the <u>{@code add}</u> methods to put items in the queue and then call
* {@link #waitForResults()}.</li>
* <li>For non-blocking usage, simply call
* {@link #process(Iterator, Consumer)}, passing the consumer of the results.</li>
* </ol>
* <p>
*
* @param <I> The input data needed by the supplied {@link QCallback}
* @param <R> The result data (can be the same as {@link I} if there is no result) returned
* @param <R> The result data (can be the same as {@code I} if there is no result) returned
* by the {@link QCallback#process(Object, TaskMonitor)} method.
*/
public class DecompilerConcurrentQ<I, R> {
private ConcurrentQ<I, R> queue;
private Consumer<R> resultConsumer = Dummy.consumer();
public DecompilerConcurrentQ(QCallback<I, R> callback, TaskMonitor monitor) {
this(callback, AutoAnalysisManager.getSharedAnalsysThreadPool(), monitor);
@ -51,6 +64,7 @@ public class DecompilerConcurrentQ<I, R> {
.setCollectResults(true)
.setThreadPool(pool)
.setMonitor(monitor)
.setListener(new InternalResultListener())
.build(callback);
// @formatter:on
}
@ -67,6 +81,18 @@ public class DecompilerConcurrentQ<I, R> {
queue.add(i);
}
/**
* Adds all items to the queue for processing. The results will be passed to the given consumer
* as they are produced.
*
* @param functions the functions to process
* @param consumer the results consumer
*/
public void process(Iterator<I> functions, Consumer<R> consumer) {
this.resultConsumer = Objects.requireNonNull(consumer);
addAll(functions);
}
/**
* Waits for all results to be delivered. The client is responsible for processing the
* results and handling any exceptions that may have occurred.
@ -75,15 +101,12 @@ public class DecompilerConcurrentQ<I, R> {
* @throws InterruptedException if interrupted while waiting
*/
public Collection<QResult<I, R>> waitForResults() throws InterruptedException {
Collection<QResult<I, R>> results = null;
try {
results = queue.waitForResults();
return queue.waitForResults();
}
finally {
queue.dispose();
}
return results;
}
/**
@ -95,7 +118,12 @@ public class DecompilerConcurrentQ<I, R> {
* @throws Exception any exception that is encountered while processing items.
*/
public void waitUntilDone() throws InterruptedException, Exception {
queue.waitUntilDone();
try {
queue.waitUntilDone();
}
finally {
queue.dispose();
}
}
public void dispose() {
@ -125,4 +153,23 @@ public class DecompilerConcurrentQ<I, R> {
"Unable to shutdown all tasks in " + timeoutSeconds + " " + TimeUnit.SECONDS);
}
}
private class InternalResultListener implements QItemListener<I, R> {
@Override
public void itemProcessed(QResult<I, R> result) {
try {
R r = result.getResult();
if (r != null) {
resultConsumer.accept(r);
}
}
catch (Throwable t) {
// This code is an asynchronous callback. Handle the exception the same way as
// the waitXyz() method do, which is to shutdown the queue.
Msg.error(this, "Unexpected exception getting Decompiler result", t);
queue.dispose();
}
}
}
}

View file

@ -59,7 +59,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
Set<Function> functions = filterFunctions(program, dataType, monitor);
try {
ParallelDecompiler.decompileFunctions(qCallback, program, functions, monitor);
ParallelDecompiler.decompileFunctions(qCallback, functions, monitor);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt(); // reset the flag
@ -83,7 +83,7 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
Set<Function> functions = filterFunctions(program, dataType, monitor);
try {
ParallelDecompiler.decompileFunctions(qCallback, program, functions, monitor);
ParallelDecompiler.decompileFunctions(qCallback, functions, monitor);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt(); // reset the flag

View file

@ -26,9 +26,7 @@ import org.apache.commons.lang3.StringUtils;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import docking.action.ActionContextProvider;
import docking.action.DockingActionIf;
import docking.actions.ActionAdapter;
import docking.action.*;
import docking.actions.KeyBindingUtils;
import docking.event.mouse.GMouseListenerAdapter;
import docking.help.HelpService;
@ -627,7 +625,7 @@ public class DialogComponentProvider
public void setStatusText(String message, MessageType type, boolean alert) {
String text = StringUtils.isBlank(message) ? " " : message;
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetStatusText(text, type, alert));
Swing.runIfSwingOrRunLater(() -> doSetStatusText(text, type, alert));
}
private void doSetStatusText(String text, MessageType type, boolean alert) {
@ -659,7 +657,7 @@ public class DialogComponentProvider
*/
protected void alertMessage(Callback alertFinishedCallback) {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
Swing.runIfSwingOrRunLater(() -> {
doAlertMessage(alertFinishedCallback);
});
}
@ -801,7 +799,7 @@ public class DialogComponentProvider
*/
@Override
public void clearStatusText() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
Swing.runIfSwingOrRunLater(() -> {
statusLabel.setText(" ");
updateStatusToolTip();
});
@ -1171,18 +1169,6 @@ public class DialogComponentProvider
}
}
/**
* Add an action to this dialog. Only actions with icons are added to the toolbar.
* @param action popup menu action
*/
public void addAction(final DockingActionIf action) {
dialogActions.add(action);
addToolbarAction(action);
popupManager.addAction(action);
registerActionKeyBinding(action);
}
public Set<DockingActionIf> getActions() {
return new HashSet<>(dialogActions);
}
@ -1203,36 +1189,22 @@ public class DialogComponentProvider
actionMap.put(action, button);
}
private void registerActionKeyBinding(DockingActionIf dockingAction) {
String name = dockingAction.getName();
KeyStroke stroke = dockingAction.getKeyBinding();
if (stroke == null) {
return;
}
ActionAdapter actionAdapter = new ActionAdapter(dockingAction, this);
Object binding = null; // old binding for keyStroke;
/**
* Add an action to this dialog. Only actions with icons are added to the toolbar.
* Note, if you add an action to this dialog, do not also add the action to
* the tool, as this dialog will do that for you.
* @param action the action
*/
public void addAction(final DockingActionIf action) {
dialogActions.add(action);
addToolbarAction(action);
popupManager.addAction(action);
InputMap imap = rootPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
if (imap != null) {
binding = imap.get(stroke);
imap.put(stroke, name);
}
ActionMap amap = rootPanel.getActionMap();
if (amap != null) {
if (binding != null) {
Action action = amap.get(binding);
if (action != null) {
if (action instanceof DockingActionIf) {
throw new AssertException(
"Attempted to register more than one acton with the same keybinding to this dialog! " +
stroke);
}
actionAdapter.setDefaultAction(action);
}
}
amap.put(name, actionAdapter);
}
// add the action to the tool in order get key event management (key bindings
// options and key event processing)
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
Tool tool = dwm.getTool();
tool.addAction(new DialogActionProxy(action));
}
public void removeAction(DockingActionIf action) {
@ -1347,51 +1319,23 @@ public class DialogComponentProvider
}
//==================================================================================================
// Testing...
//==================================================================================================
/**
* A placeholder action that we register with the tool in order to get key event management
*/
private class DialogActionProxy extends DockingActionProxy {
public static void main(String[] args) throws Exception {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception exc) {
Msg.error(DialogComponentProvider.class, "Error loading L&F: " + exc, exc);
}
JFrame frame = new JFrame("What");
Joe joe = new Joe("My Test Dialog Component", true);
frame.setVisible(true);
JDialog d = DockingDialog.createDialog(frame, joe, null);
d.setVisible(true);
Thread.sleep(2000);
d = DockingDialog.createDialog(frame, joe, null);
d.setVisible(true);
frame.setVisible(false);
System.exit(0);
}
static class Joe extends DialogComponentProvider {
Joe(String title, boolean modal) {
super(title, modal);
addOKButton();
addCancelButton();
addWorkPanel(new JButton("TEST"));
public DialogActionProxy(DockingActionIf dockingAction) {
super(dockingAction);
}
@Override
protected void okCallback() {
this.setStatusText("OK");
// Task t = new BusyTask();
// executeProgressTask(t, 500);
setOkEnabled(false);
public boolean isAddToPopup(ActionContext context) {
return false;
}
@Override
protected void cancelCallback() {
this.setStatusText("Cancel");
clearScheduledTask();
super.cancelCallback();
public ToolBarData getToolBarData() {
return null;
}
}
}

View file

@ -1245,7 +1245,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
root.update(); // do this before rebuilding the menu, as new windows may be opened
buildComponentMenu();
SystemUtilities.runSwingLater(() -> updateFocus());
Swing.runLater(() -> updateFocus());
}
private void updateFocus() {
@ -1292,7 +1292,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Window activeWindow = kfm.getActiveWindow();
if (activeWindow == null) {
@ -1480,7 +1480,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
// Note: do this later, since, during this callback, component providers can do
// things that break focus (e.g., launch a modal dialog). By doing this later,
// it gives the java focus engine a chance to get in the correct state.
SystemUtilities.runSwingLater(() -> setFocusedComponent(placeholder));
Swing.runLater(() -> setFocusedComponent(placeholder));
}
private boolean ensureDockableComponentContainsFocusOwner(Component newFocusComponent,
@ -1515,7 +1515,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
// else use last focus component in window
WindowNode node = root.getNodeForWindow(window);
if (node == null) {
throw new AssertException("Cant find node for window!!");
return null;
}
// NOTE: We only allow focus within a window on a component that belongs to within a
@ -1610,17 +1610,23 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
private boolean isMyWindow(Window win) {
if (root == null) {
if (root == null || win == null) {
return false;
}
if (root.getMainWindow() == win) {
Window rootFrame = root.getMainWindow();
if (rootFrame == win) {
return true;
}
Iterator<DetachedWindowNode> iter = root.getDetachedWindows().iterator();
while (iter.hasNext()) {
if (iter.next().getWindow() == win) {
return true;
}
WindowNode node = root.getNodeForWindow(win);
if (node != null) {
return true;
}
// see if the given window is a child of the root node's frame
if (SwingUtilities.isDescendingFrom(win, rootFrame)) {
return true;
}
return false;
}

View file

@ -15,7 +15,6 @@
*/
package docking;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
@ -94,7 +93,7 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
return isSelected();
}
/** This method only functions if this class was created with an action */
// This method only functions if this class was created with an action
protected void doActionPerformed(ActionEvent e) {
setSelected(!isSelected()); // toggle
@ -105,7 +104,7 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
buttonAction.actionPerformed(e);
}
private void doPropertyChange(PropertyChangeEvent e) {
protected void doPropertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if (name.equals("enabled")) {
setEnabled(((Boolean) e.getNewValue()).booleanValue());
@ -165,14 +164,14 @@ public class EmptyBorderToggleButton extends EmptyBorderButton {
return b;
}
DefaultButtonModel model = (DefaultButtonModel) bm;
ButtonGroup group = model.getGroup();
DefaultButtonModel buttonModel = (DefaultButtonModel) bm;
ButtonGroup group = buttonModel.getGroup();
if (group == null) {
return b;
}
group.setSelected(model, b);
boolean isSelected = group.isSelected(model);
group.setSelected(buttonModel, b);
boolean isSelected = group.isSelected(buttonModel);
return isSelected;
}

View file

@ -127,7 +127,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
}
// some known special cases that we don't wish to process
if (!isValidContextForKeyStroke(KeyStroke.getKeyStrokeForEvent(event))) {
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(event);
if (!isValidContextForKeyStroke(ks)) {
return false;
}
@ -218,7 +219,13 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
private boolean isValidContextForKeyStroke(KeyStroke keyStroke) {
Window activeWindow = focusProvider.getActiveWindow();
if (activeWindow instanceof DockingDialog) {
return false; // we don't want to process our key bindings when in DockingDialogs
// This is legacy code, for which the reasons it exists cannot be recalled. We
// speculate that odd things can happen when keybindings are processed with model
// dialogs open. For now, do not let key bindings get processed for modal dialogs.
// This can be changed in the future if needed.
DockingDialog dialog = (DockingDialog) activeWindow;
return !dialog.isModal();
}
return true; // default case; allow it through
}

View file

@ -15,6 +15,7 @@
*/
package docking.action;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.util.*;
@ -87,19 +88,16 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
*/
@Override
public boolean isEnabled() {
// always return true so we can report the status message
// when none of the actions is enabled...
// always return true so we can report the status message when all actions are disabled
return true;
}
/**
* Enables or disables the action. This affects all uses
* of the action. Note that for popups, this affects whether or
* not the option is "grayed out", not whether the action is added
* Enables or disables the action. This affects all uses of the action. Note that for popups,
* this affects whether or not the option is "grayed out", not whether the action is added
* to the popup.
*
* @param newValue true to enable the action, false to
* disable it
* @param newValue true to enable the action, false to disable it
* @see Action#setEnabled
*/
@Override
@ -111,17 +109,10 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
}
}
/**
* Invoked when an action occurs.
*/
@Override
public void actionPerformed(final ActionEvent event) {
// Build list of actions which are valid in current context
ComponentProvider localProvider = tool.getActiveComponentProvider();
ActionContext localContext = getLocalContext(localProvider);
localContext.setSourceObject(event.getSource());
List<ExecutableKeyActionAdapter> list = getValidContextActions(localContext);
List<ExecutableKeyActionAdapter> list = getActionsForCurrentContext(event.getSource());
// If menu active, disable all key bindings
if (ignoreActionWhileMenuShowing()) {
@ -237,10 +228,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
@Override
public KeyBindingPrecedence getKeyBindingPrecedence() {
ComponentProvider localProvider = tool.getActiveComponentProvider();
ActionContext localContext = getLocalContext(localProvider);
List<ExecutableKeyActionAdapter> validActions = getValidContextActions(localContext);
List<ExecutableKeyActionAdapter> validActions = getActionsForCurrentContext(null);
if (validActions.isEmpty()) {
return null; // a signal that no actions are valid for the current context
}
@ -254,6 +242,29 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
return action.getKeyBindingData().getKeyBindingPrecedence();
}
private List<ExecutableKeyActionAdapter> getActionsForCurrentContext(Object eventSource) {
DockingWindowManager dwm = tool.getWindowManager();
Window window = dwm.getActiveWindow();
if (window instanceof DockingDialog) {
DockingDialog dockingDialog = (DockingDialog) window;
DialogComponentProvider provider = dockingDialog.getDialogComponent();
if (provider == null) {
// this can happen if the dialog is closed during key event processing
return Collections.emptyList();
}
ActionContext context = provider.getActionContext(null);
List<ExecutableKeyActionAdapter> validActions = getValidContextActions(context);
return validActions;
}
ComponentProvider localProvider = dwm.getActiveComponentProvider();
ActionContext localContext = getLocalContext(localProvider);
localContext.setSourceObject(eventSource);
List<ExecutableKeyActionAdapter> validActions = getValidContextActions(localContext);
return validActions;
}
public List<DockingActionIf> getActions() {
List<DockingActionIf> list = new ArrayList<>(actions.size());
for (ActionData actionData : actions) {

View file

@ -18,6 +18,7 @@ package docking.action;
import javax.swing.Icon;
import docking.DockingUtils;
import generic.json.Json;
import ghidra.util.SystemUtilities;
public class ToolBarData {
@ -106,4 +107,9 @@ public class ToolBarData {
ownerAction.firePropertyChanged(DockingActionIf.TOOLBAR_DATA_PROPERTY, oldData, this);
}
}
@Override
public String toString() {
return Json.toString(this, "icon", "toolBarGroup", "toolBarSubGroup");
}
}

View file

@ -27,14 +27,15 @@ import java.util.stream.Collectors;
import javax.swing.*;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import docking.Tool;
import docking.DockingUtils;
import docking.Tool;
import docking.action.*;
import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.options.ToolOptions;
@ -666,6 +667,8 @@ public class KeyBindingUtils {
* and we want it to look like: "Ctrl-M".
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
* and we want it to look like: "Ctrl-M".
* <br>In Java 11 we have seen toString() values get printed with repeated text, such
* as: "shift ctrl pressed SHIFT". We want to trim off the repeated modifiers.
*
* @param keyStroke the key stroke
* @return the string value; the empty string if the key stroke is null
@ -685,19 +688,19 @@ public class KeyBindingUtils {
// get the character used in the key stroke
int firstIndex = keyString.lastIndexOf(' ') + 1;
int ctrlIndex = keyString.indexOf(CTRL, firstIndex);
int ctrlIndex = indexOf(keyString, CTRL, firstIndex);
if (ctrlIndex >= 0) {
firstIndex = ctrlIndex + CTRL.length();
}
int altIndex = keyString.indexOf(ALT, firstIndex);
int altIndex = indexOf(keyString, ALT, firstIndex);
if (altIndex >= 0) {
firstIndex = altIndex + ALT.length();
}
int shiftIndex = keyString.indexOf(SHIFT, firstIndex);
int shiftIndex = indexOf(keyString, SHIFT, firstIndex);
if (shiftIndex >= 0) {
firstIndex = shiftIndex + SHIFT.length();
}
int metaIndex = keyString.indexOf(META, firstIndex);
int metaIndex = indexOf(keyString, META, firstIndex);
if (metaIndex >= 0) {
firstIndex = metaIndex + META.length();
}
@ -714,18 +717,31 @@ public class KeyBindingUtils {
StringBuilder buffy = new StringBuilder();
if (isShift(modifiers)) {
buffy.insert(0, SHIFT + MODIFIER_SEPARATOR);
keyString = removeIgnoreCase(keyString, SHIFT);
}
if (isAlt(modifiers)) {
buffy.insert(0, ALT + MODIFIER_SEPARATOR);
keyString = removeIgnoreCase(keyString, ALT);
}
if (isControl(modifiers)) {
buffy.insert(0, CTRL + MODIFIER_SEPARATOR);
keyString = removeIgnoreCase(keyString, CONTROL);
}
if (isMeta(modifiers)) {
buffy.insert(0, META + MODIFIER_SEPARATOR);
keyString = removeIgnoreCase(keyString, META);
}
buffy.append(keyString);
return buffy.toString();
String text = buffy.toString().trim();
if (text.endsWith(MODIFIER_SEPARATOR)) {
text = text.substring(0, text.length() - 1);
}
return text;
}
private static int indexOf(String source, String search, int offset) {
return StringUtils.indexOfIgnoreCase(source, search, offset);
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the

View file

@ -18,6 +18,8 @@ package docking.actions;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Action;
import javax.swing.KeyStroke;
@ -41,6 +43,9 @@ import util.CollectionUtils;
*/
public class ToolActions implements DockingToolActions, PropertyChangeListener {
// matches the full action name (e.g., "Action Name (Owner Name)"
private Pattern ACTION_NAME_PATTERN = Pattern.compile("(.+) \\((.+)\\)");
private ActionToGuiHelper actionGuiHelper;
/*
@ -57,6 +62,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
private ToolOptions keyBindingOptions;
private Tool dockingTool;
private KeyBindingsManager keyBindingsManager;
private OptionsChangeListener optionChangeListener = (options, optionName, oldValue,
newValue) -> updateKeyBindingsFromOptions(options, optionName, (KeyStroke) newValue);
/**
* Construct an ActionManager
@ -69,6 +76,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
this.actionGuiHelper = actionToGuiHelper;
this.keyBindingsManager = new KeyBindingsManager(tool);
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
this.keyBindingOptions.addOptionsChangeListener(optionChangeListener);
createReservedKeyBindings();
SharedActionRegistry.installSharedActions(tool, this);
@ -356,13 +364,33 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
return actionsByNameByOwner.get(owner).get(name);
}
private void updateKeyBindingsFromOptions(ToolOptions options, String optionName,
KeyStroke newKs) {
// note: the 'shared actions' update themselves, so we only need to handle standard actions
Matcher matcher = ACTION_NAME_PATTERN.matcher(optionName);
matcher.find();
String name = matcher.group(1);
String owner = matcher.group(2);
Set<DockingActionIf> actions = actionsByNameByOwner.get(owner).get(name);
for (DockingActionIf action : actions) {
KeyStroke oldKs = action.getKeyBinding();
if (Objects.equals(oldKs, newKs)) {
continue; // prevent bouncing
}
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
return;
}
DockingAction action = (DockingAction) evt.getSource();
DockingActionIf action = (DockingActionIf) evt.getSource();
if (!action.getKeyBindingType().isManaged()) {
// this reads unusually, but we need to notify the tool to rebuild its 'Window' menu
// in the case that this action is one of the tool's special actions
@ -376,13 +404,12 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
newKeyStroke = newKeyBindingData.getKeyBinding();
}
Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null);
KeyStroke optKeyStroke = keyBindingOptions.getKeyStroke(action.getFullName(), null);
if (newKeyStroke == null) {
opt.removeOption(action.getFullName());
keyBindingOptions.removeOption(action.getFullName());
}
else if (!newKeyStroke.equals(optKeyStroke)) {
opt.setKeyStroke(action.getFullName(), newKeyStroke);
keyBindingOptions.setKeyStroke(action.getFullName(), newKeyStroke);
keyBindingsChanged();
}
}
@ -424,6 +451,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
*
* @param placeholder the placeholder containing information related to the action it represents
*/
@Override
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder) {
String name = placeholder.getName();

View file

@ -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 docking.menu;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.KeyStroke;
import org.apache.commons.lang3.StringUtils;
import docking.action.DockingActionIf;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
import ghidra.util.StringUtilities;
class DockingToolBarUtils {
private static final String START_KEYBINDING_TEXT = "<BR><HR><CENTER>(";
private static final String END_KEYBINDNIG_TEXT = ")</CENTER>";
/**
* Sets the given button's tooltip text to match that of the given action
* @param button the button
* @param action the action
*/
static void setToolTipText(JButton button, DockingActionIf action) {
String toolTipText = getToolTipText(action);
String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding());
if (keyBindingText != null) {
button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText));
}
else {
button.setToolTipText(toolTipText);
}
}
private static String combingToolTipTextWithKeyBinding(String toolTipText,
String keyBindingText) {
StringBuilder buffy = new StringBuilder(toolTipText);
if (StringUtilities.startsWithIgnoreCase(toolTipText, "<HTML>")) {
String endHTMLTag = "</HTML>";
int closeTagIndex = StringUtils.indexOfIgnoreCase(toolTipText, endHTMLTag);
if (closeTagIndex < 0) {
// no closing tag, which is acceptable
buffy.append(START_KEYBINDING_TEXT)
.append(keyBindingText)
.append(END_KEYBINDNIG_TEXT);
}
else {
// remove the closing tag, put on our text, and then put the tag back on
buffy.delete(closeTagIndex, closeTagIndex + endHTMLTag.length() + 1);
buffy.append(START_KEYBINDING_TEXT)
.append(keyBindingText)
.append(END_KEYBINDNIG_TEXT)
.append(endHTMLTag);
}
return buffy.toString();
}
// plain text (not HTML)
return toolTipText + " (" + keyBindingText + ")";
}
private static String getToolTipText(DockingActionIf action) {
String description = action.getDescription();
if (!StringUtils.isEmpty(description)) {
return description;
}
return action.getName();
}
private static String getKeyBindingAcceleratorText(JButton button, KeyStroke keyStroke) {
if (keyStroke == null) {
return null;
}
// This code is based on that of BasicMenuItemUI
StringBuilder builder = new StringBuilder();
int modifiers = keyStroke.getModifiers();
if (modifiers > 0) {
builder.append(InputEvent.getModifiersExText(modifiers));
// The Aqua LaF does not use the '+' symbol between modifiers
if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) {
builder.append('+');
}
}
int keyCode = keyStroke.getKeyCode();
if (keyCode != 0) {
builder.append(KeyEvent.getKeyText(keyCode));
}
else {
builder.append(keyStroke.getKeyChar());
}
return builder.toString();
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +16,7 @@
package docking.menu;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import docking.DockingWindowManager;
import docking.EmptyBorderToggleButton;
@ -27,7 +27,7 @@ import docking.action.*;
* the override notes below).
*/
public class DockingToolbarButton extends EmptyBorderToggleButton {
private DockingActionIf dockableAction;
private DockingActionIf dockingAction;
private ActionContextProvider contextProvider;
public DockingToolbarButton(DockingActionIf action, ActionContextProvider contextProvider) {
@ -36,21 +36,47 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
setFocusable(false);
addMouseListener(new MouseOverMouseListener());
action.addPropertyChangeListener(propertyChangeListener);
// make sure this button gets our specialized tooltip
DockingToolBarUtils.setToolTipText(this, dockingAction);
}
@Override
protected void initFromAction(DockingActionIf action) {
dockableAction = action;
dockingAction = action;
super.initFromAction(action);
}
@Override
protected void doActionPerformed(ActionEvent e) {
if (dockableAction instanceof ToggleDockingActionIf) {
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) dockableAction;
if (dockingAction instanceof ToggleDockingActionIf) {
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) dockingAction;
toggleAction.setSelected(!toggleAction.isSelected());
}
dockableAction.actionPerformed(contextProvider.getActionContext(null));
dockingAction.actionPerformed(contextProvider.getActionContext(null));
}
@Override
protected void doPropertyChange(PropertyChangeEvent e) {
super.doPropertyChange(e);
String name = e.getPropertyName();
if (name.equals(DockingActionIf.ENABLEMENT_PROPERTY)) {
setEnabled(((Boolean) e.getNewValue()).booleanValue());
}
else if (name.equals(DockingActionIf.DESCRIPTION_PROPERTY)) {
DockingToolBarUtils.setToolTipText(this, dockingAction);
}
else if (name.equals(DockingActionIf.TOOLBAR_DATA_PROPERTY)) {
ToolBarData toolBarData = (ToolBarData) e.getNewValue();
setIcon(toolBarData == null ? null : toolBarData.getIcon());
}
else if (name.equals(ToggleDockingActionIf.SELECTED_STATE_PROPERTY)) {
setSelected((Boolean) e.getNewValue());
}
else if (name.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
DockingToolBarUtils.setToolTipText(this, dockingAction);
}
}
@Override
@ -58,23 +84,23 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
// toggle buttons or regular non-toggle buttons, which dictates whether this
// button is selected (non-toggle buttons are not selectable).
protected boolean isButtonSelected() {
if (dockableAction instanceof ToggleDockingAction) {
return ((ToggleDockingAction) dockableAction).isSelected();
if (dockingAction instanceof ToggleDockingAction) {
return ((ToggleDockingAction) dockingAction).isSelected();
}
return false;
}
public DockingActionIf getDockingAction() {
return dockableAction;
return dockingAction;
}
@Override
// overridden to reflect the potentiality that our action is a toggle action
public void setSelected(boolean b) {
if (dockableAction instanceof ToggleDockingActionIf) {
if (dockingAction instanceof ToggleDockingActionIf) {
// only change the state if the action is a toggle action; doing otherwise would
// break the DockableAction
((ToggleDockingActionIf) dockableAction).setSelected(b);
((ToggleDockingActionIf) dockingAction).setSelected(b);
}
super.setSelected(b);
}
@ -82,15 +108,15 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
@Override
// overridden to reflect the potentiality that our action is a toggle action
public boolean isSelected() {
if (dockableAction instanceof ToggleDockingActionIf) {
return ((ToggleDockingActionIf) dockableAction).isSelected();
if (dockingAction instanceof ToggleDockingActionIf) {
return ((ToggleDockingActionIf) dockingAction).isSelected();
}
return super.isSelected();
}
@Override
public void removeListeners() {
dockableAction.removePropertyChangeListener(propertyChangeListener);
dockingAction.removePropertyChangeListener(propertyChangeListener);
super.removeListeners();
}
@ -99,7 +125,7 @@ public class DockingToolbarButton extends EmptyBorderToggleButton {
@Override
public void mouseEntered(MouseEvent me) {
DockingWindowManager.setMouseOverAction(dockableAction);
DockingWindowManager.setMouseOverAction(dockingAction);
}
@Override

View file

@ -19,23 +19,17 @@ import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import docking.*;
import docking.action.*;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
import ghidra.util.StringUtilities;
/**
* Class to manager toolbar buttons.
*/
public class ToolBarItemManager implements PropertyChangeListener, ActionListener, MouseListener {
private static final String START_KEYBINDING_TEXT = "<BR><HR><CENTER>(";
private static final String END_KEYBINDNIG_TEXT = ")</CENTER>";
private DockingActionIf toolBarAction;
private JButton toolBarButton;
private final DockingWindowManager windowManager;
@ -51,15 +45,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
action.addPropertyChangeListener(this);
}
/**
* Returns the group for this item.
*/
String getGroup() {
return toolBarAction.getToolBarData().getToolBarGroup();
}
/**
* Returns a button for this items action.
* Returns a button for this items action
* @return the button
*/
public JButton getButton() {
if (toolBarButton == null) {
@ -74,81 +66,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
button.addActionListener(this);
button.addMouseListener(this);
button.setName(action.getName());
setToolTipText(button, action, getToolTipText(action));
DockingToolBarUtils.setToolTipText(button, action);
return button;
}
private void setToolTipText(JButton button, DockingActionIf action, String toolTipText) {
String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding());
if (keyBindingText != null) {
button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText));
}
else {
button.setToolTipText(toolTipText);
}
javax.swing.ToolTipManager instance = javax.swing.ToolTipManager.sharedInstance();
// instance.unregisterComponent( button );
}
private String combingToolTipTextWithKeyBinding(String toolTipText, String keyBindingText) {
StringBuilder buffy = new StringBuilder(toolTipText);
if (StringUtilities.startsWithIgnoreCase(toolTipText, "<HTML>")) {
String endHTMLTag = "</HTML>";
int closeTagIndex = StringUtils.indexOfIgnoreCase(toolTipText, endHTMLTag);
if (closeTagIndex < 0) {
// no closing tag, which is acceptable
buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append(
END_KEYBINDNIG_TEXT);
}
else {
// remove the closing tag, put on our text, and then put the tag back on
buffy.delete(closeTagIndex, closeTagIndex + endHTMLTag.length() + 1);
buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append(
END_KEYBINDNIG_TEXT).append(endHTMLTag);
}
return buffy.toString();
}
// plain text (not HTML)
return toolTipText + " (" + keyBindingText + ")";
}
private String getToolTipText(DockingActionIf action) {
String description = action.getDescription();
if (!StringUtils.isEmpty(description)) {
return description;
}
return action.getName();
}
private String getKeyBindingAcceleratorText(JButton button, KeyStroke keyStroke) {
if (keyStroke == null) {
return null;
}
// This code is based on that of BasicMenuItemUI
StringBuilder builder = new StringBuilder();
int modifiers = keyStroke.getModifiers();
if (modifiers > 0) {
builder.append(InputEvent.getModifiersExText(modifiers));
// The Aqua LaF does not use the '+' symbol between modifiers
if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) {
builder.append('+');
}
}
int keyCode = keyStroke.getKeyCode();
if (keyCode != 0) {
builder.append(KeyEvent.getKeyText(keyCode));
}
else {
builder.append(keyStroke.getKeyChar());
}
return builder.toString();
}
/**
* Returns the action being managed.
* Returns the action being managed
* @return the action
*/
public DockingActionIf getAction() {
return toolBarAction;
@ -172,7 +96,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
toolBarButton.setEnabled(((Boolean) e.getNewValue()).booleanValue());
}
else if (name.equals(DockingActionIf.DESCRIPTION_PROPERTY)) {
setToolTipText(toolBarButton, toolBarAction, (String) e.getNewValue());
DockingToolBarUtils.setToolTipText(toolBarButton, toolBarAction);
}
else if (name.equals(DockingActionIf.TOOLBAR_DATA_PROPERTY)) {
ToolBarData toolBarData = (ToolBarData) e.getNewValue();
@ -182,7 +106,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
toolBarButton.setSelected((Boolean) e.getNewValue());
}
else if (name.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
setToolTipText(toolBarButton, toolBarAction, getToolTipText(toolBarAction));
DockingToolBarUtils.setToolTipText(toolBarButton, toolBarAction);
}
}

View file

@ -1716,6 +1716,21 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
}
}
/**
* Fires a {@link KeyListener#keyPressed(KeyEvent)},
* {@link KeyListener#keyTyped(KeyEvent)}
* and {@link KeyListener#keyReleased(KeyEvent)} for the given key stroke
*
* @param c the destination component
* @param ks the key stroke
*/
public static void triggerKey(Component c, KeyStroke ks) {
int modifiers = ks.getModifiers();
char keyChar = ks.getKeyChar();
int keyCode = ks.getKeyCode();
triggerKey(c, modifiers, keyCode, keyChar);
}
/**
* Fires a {@link KeyListener#keyPressed(KeyEvent)}, {@link KeyListener#keyTyped(KeyEvent)}
* and {@link KeyListener#keyReleased(KeyEvent)} for the given key code and char.

View file

@ -136,7 +136,7 @@ import ghidra.util.task.TaskMonitor;
* @param <I> The type of the items to be processed.
* @param <R> The type of objects resulting from processing an item; if you don't care about the
* return value, then make this value whatever you want, like <code>Object</code> or the
* same value as {@link I} and return null from {@link QCallback#process(Object, TaskMonitor)}.
* same value as {@code I} and return null from {@link QCallback#process(Object, TaskMonitor)}.
*/
public class ConcurrentQ<I, R> {
@ -452,6 +452,8 @@ public class ConcurrentQ<I, R> {
* 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.
* @throws InterruptedException if this call was interrupted.
*/
@ -596,7 +598,7 @@ public class ConcurrentQ<I, R> {
if (collectResults) {
resultList.add(result);
}
tracker.InProgressitemCompletedOrCancelled();
tracker.inProgressItemCompletedOrCancelled();
fillOpenProcessingSlots();
if (result.hasError() && unhandledException == null) {
@ -729,11 +731,8 @@ public class ConcurrentQ<I, R> {
* Simple connector for traditional TaskMonitor and a task from the ConcurrentQ. This adapter
* adds a cancel listener to the TaskMonitor and when cancelled is called on the monitor,
* it cancels the currently running (scheduled on the thread pool) and leaves the waiting
* tasks alone. It also implements a QProgressListener and adds itselve to the concurrentQ so
* tasks alone. It also implements a QProgressListener and adds itself to the concurrentQ so
* that it gets progress events and messages and sets them on the task monitor.
*
* @param <I>
* @param <R>
*/
private class QMonitorAdapter implements QProgressListener<I>, CancelledListener {

View file

@ -15,13 +15,12 @@
*/
package generic.concurrent;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import ghidra.util.task.TaskMonitor;
/**
* A helper class to build up the potentially complicated {@link ConcurrentQ}.
* <P>
@ -71,7 +70,7 @@ public class ConcurrentQBuilder<I, R> {
private boolean collectResults;
private int maxInProgress;
private boolean jobsReportProgress = false;
private TaskMonitor monitor = TaskMonitorAdapter.DUMMY_MONITOR;
private TaskMonitor monitor = TaskMonitor.DUMMY;
private boolean cancelClearsAllJobs = true;
/**
@ -179,9 +178,14 @@ public class ConcurrentQBuilder<I, R> {
}
/**
* 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.
* @return this builder
* @see ConcurrentQ#setMonitor(TaskMonitor, boolean)
* <p>
* The default value is <code>true</code>.
*/
public ConcurrentQBuilder<I, R> setCancelClearsAllJobs(boolean clearAllJobs) {
this.cancelClearsAllJobs = clearAllJobs;
@ -191,7 +195,7 @@ public class ConcurrentQBuilder<I, R> {
public ConcurrentQ<I, R> build(QCallback<I, R> callback) {
ConcurrentQ<I, R> concurrentQ =
new ConcurrentQ<I, R>(callback, getQueue(), getThreadPool(), listener, collectResults,
new ConcurrentQ<>(callback, getQueue(), getThreadPool(), listener, collectResults,
maxInProgress, jobsReportProgress);
if (monitor != null) {
@ -216,6 +220,6 @@ public class ConcurrentQBuilder<I, R> {
if (queue != null) {
return queue;
}
return new LinkedList<I>();
return new LinkedList<>();
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -60,7 +59,7 @@ class ProgressTracker {
}
}
void InProgressitemCompletedOrCancelled() {
void inProgressItemCompletedOrCancelled() {
lock.lock();
try {
completedOrCancelledCount++;

View file

@ -0,0 +1,139 @@
/* ###
* 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 generic.json;
import java.lang.reflect.Field;
import java.util.Arrays;
import org.apache.commons.lang3.builder.*;
/**
* A utility class to format strings in JSON format. This is useful for easily generating
* {@code toString()} representations of objects.
*/
public class Json extends ToStringStyle {
public static final JsonWithNewlinesToStringStyle WITH_NEWLINES =
new JsonWithNewlinesToStringStyle();
/**
* A {@link ToStringStyle} inspired by {@link ToStringStyle#JSON_STYLE} that places
* object fields on newlines for more readability
*/
public static class JsonWithNewlinesToStringStyle extends ToStringStyle {
private JsonWithNewlinesToStringStyle() {
this.setUseClassName(false);
this.setUseIdentityHashCode(false);
this.setContentStart("{\n\t");
this.setContentEnd("\n}");
this.setArrayStart("[");
this.setArrayEnd("]");
this.setFieldSeparator(",\n\t");
this.setFieldNameValueSeparator(":");
this.setNullText("null");
this.setSummaryObjectStartText("\"<");
this.setSummaryObjectEndText(">\"");
this.setSizeStartText("\"<size=");
this.setSizeEndText(">\"");
}
}
/**
* Creates a Json string representation of the given object and all of its fields. To exclude
* some fields, call {@link #toStringExclude(Object, String...)}. To only include particular
* fields, call {@link #appendToString(StringBuffer, String)}.
* @param o the object
* @return the string
*/
public static String toString(Object o) {
return ToStringBuilder.reflectionToString(o, Json.WITH_NEWLINES);
}
/**
* Creates a Json string representation of the given object and the given fields
* @param o the object
* @param includFields the fields to include
* @return the string
*/
public static String toString(Object o, String... includFields) {
InclusiveReflectionToStringBuilder builder = new InclusiveReflectionToStringBuilder(o);
builder.setIncludeFieldNames(includFields);
return builder.toString();
}
/**
* Creates a Json string representation of the given object and all of its fields except for
* those in the given exclusion list
* @param o the object
* @param excludedFields the excluded field names
* @return the string
*/
public static String toStringExclude(Object o, String... excludedFields) {
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(o,
Json.WITH_NEWLINES);
builder.setExcludeFieldNames(excludedFields);
return builder.toString();
}
// Future: update this class to use the order of the included fields to be the printed ordered
private static class InclusiveReflectionToStringBuilder extends ReflectionToStringBuilder {
private String[] includedNames;
public InclusiveReflectionToStringBuilder(Object object) {
super(object, WITH_NEWLINES);
}
@Override
protected boolean accept(Field field) {
if (!super.accept(field)) {
return false;
}
if (this.includedNames != null &&
Arrays.binarySearch(this.includedNames, field.getName()) >= 0) {
return true;
}
return false;
}
/**
* Sets the names to be included
* @param includeFieldNamesParam the names
* @return this builder
*/
public ReflectionToStringBuilder setIncludeFieldNames(
final String... includeFieldNamesParam) {
if (includeFieldNamesParam == null) {
this.includedNames = null;
}
else {
this.includedNames = includeFieldNamesParam;
Arrays.sort(this.includedNames);
}
return this;
}
}
}

View file

@ -23,6 +23,8 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import generic.json.Json;
/**
* Class with static methods that deal with string manipulation.
*/
@ -442,7 +444,6 @@ public class StringUtilities {
return true;
}
/**
* Convert tabs in the given string to spaces using
* a default tab width of 8 spaces.
@ -647,7 +648,6 @@ public class StringUtilities {
return location.getWord();
}
public static WordLocation findWordLocation(String s, int index, char[] charsToAllow) {
int len = s.length();
@ -774,6 +774,18 @@ public class StringUtilities {
return new String(bytes);
}
/**
* Creates a JSON string for the given object using all of its fields. To control the
* fields that are in the result string, see {@link Json}.
*
* <P>This is here as a marker to point users to the real {@link Json} String utility.
* @param o the object for which to create a string
* @return the string
*/
public static String toStingJson(Object o) {
return Json.toString(o);
}
public static String toStringWithIndent(Object o) {
if (o == null) {
return "null";

View file

@ -25,6 +25,7 @@ import ghidra.util.exception.TimeoutException;
import ghidra.util.timer.GTimer;
import ghidra.util.timer.GTimerMonitor;
import utility.function.Callback;
import utility.function.Dummy;
/**
* A task monitor that allows clients the ability to specify a timeout after which this monitor
@ -105,7 +106,8 @@ public class TimeoutTaskMonitor implements TaskMonitor {
* @param timeoutCallback the callback to call
*/
public void setTimeoutListener(Callback timeoutCallback) {
this.timeoutCallback = Callback.dummyIfNull(timeoutCallback);
this.timeoutCallback = Dummy.ifNull(timeoutCallback);
}
/**

View file

@ -0,0 +1,109 @@
/* ###
* 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 utility.function;
import java.util.function.*;
/**
* A utility class to help create dummy stub functional interfaces
*/
public class Dummy {
/**
* Creates a dummy callback
* @return a dummy callback
*/
public static Callback callback() {
return () -> {
// no-op
};
}
/**
* Creates a dummy consumer
* @return a dummy consumer
*/
public static <T> Consumer<T> consumer() {
return t -> {
// no-op
};
}
/**
* Creates a dummy function
* @param <T> the input type
* @param <R> the result type
* @return the function
*/
public static <T, R> Function<T, R> function() {
return t -> null;
}
/**
* Creates a dummy supplier
* @param <T> the result type
* @return the supplier
*/
public static <T> Supplier<T> supplier() {
return () -> null;
}
/**
* Returns the given consumer object if it is not {@code null}. Otherwise, a {@link #consumer()}
* is returned. This is useful to avoid using {@code null}.
*
* @param c the consumer function to check for {@code null}
* @return a non-null consumer function
*/
public static <T> Consumer<T> ifNull(Consumer<T> c) {
return c == null ? consumer() : c;
}
/**
* Returns the given callback object if it is not {@code null}. Otherwise, a {@link #callback()}
* is returned. This is useful to avoid using {@code null}.
*
* @param c the callback function to check for {@code null}
* @return a non-null callback function
*/
public static Callback ifNull(Callback c) {
return c == null ? callback() : c;
}
/**
* Returns the given function object if it is not {@code null}. Otherwise, a
* {@link #function()} is returned. This is useful to avoid using {@code null}.
*
* @param <T> the input type
* @param <R> the result type
* @param f the function to check for {@code null}
* @return a non-null callback function
*/
public static <T, R> Function<T, R> ifNull(Function<T, R> f) {
return f == null ? function() : f;
}
/**
* Returns the given callback object if it is not {@code null}. Otherwise, a {@link #callback()}
* is returned. This is useful to avoid using {@code null}.
*
* @param s the supplier function to check for {@code null}
* @return a non-null callback function
*/
public static <T> Supplier<T> ifNull(Supplier<T> s) {
return s == null ? supplier() : s;
}
}