GT-2376: added new task monitor service

This commit is contained in:
adamopolous 2019-04-04 12:36:36 -04:00
parent 538cbc1226
commit f57af0b730
28 changed files with 1363 additions and 558 deletions

View file

@ -1513,12 +1513,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
return Math.max(dominantMonitor.getProgress(), slaveMonitor.getProgress()); return Math.max(dominantMonitor.getProgress(), slaveMonitor.getProgress());
} }
@Override
public void reportIssue(Issue issue) {
dominantMonitor.reportIssue(issue);
slaveMonitor.reportIssue(issue);
}
@Override @Override
public void cancel() { public void cancel() {
dominantMonitor.cancel(); dominantMonitor.cancel();
@ -1535,16 +1529,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
dominantMonitor.addCancelledListener(listener); dominantMonitor.addCancelledListener(listener);
} }
@Override
public void addIssueListener(IssueListener listener) {
dominantMonitor.addIssueListener(listener);
}
@Override
public void removeIssueListener(IssueListener listener) {
dominantMonitor.removeIssueListener(listener);
}
@Override @Override
public void setCancelEnabled(boolean enable) { public void setCancelEnabled(boolean enable) {
dominantMonitor.setCancelEnabled(enable); dominantMonitor.setCancelEnabled(enable);

View file

@ -18,9 +18,9 @@ package ghidra.app.util.headless;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.*; import ghidra.util.task.CancelledListener;
import ghidra.util.task.TaskMonitor;
/** /**
* Monitor used by Headless Analyzer for "timeout" functionality * Monitor used by Headless Analyzer for "timeout" functionality
@ -99,11 +99,6 @@ public class HeadlessTimedTaskMonitor implements TaskMonitor {
return 0; return 0;
} }
@Override
public void reportIssue(Issue issue) {
// stub
}
@Override @Override
public void cancel() { public void cancel() {
timer.cancel(); // Terminate the timer thread timer.cancel(); // Terminate the timer thread
@ -120,16 +115,6 @@ public class HeadlessTimedTaskMonitor implements TaskMonitor {
// stub // stub
} }
@Override
public void addIssueListener(IssueListener listener) {
// stub
}
@Override
public void removeIssueListener(IssueListener listener) {
// stub
}
@Override @Override
public void setCancelEnabled(boolean enable) { public void setCancelEnabled(boolean enable) {
// stub // stub

View file

@ -0,0 +1,341 @@
/* ###
* 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.atomic.*;
import org.junit.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
/**
* Tests for the {@link TaskMonitorService}
*/
public class TaskMonitorServiceTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void setup() {
// nothing to do
}
@After
public void teardown() {
// nothing to do
}
/**
* Verifies that in a single-threaded environment, the {@link TaskMonitorService}
* returns the correct instance of {@link TaskMonitor}. In this case, the initial
* monitor is registered by the {@link TaskLauncher task launcher}; the next call
* to {@link TaskMonitorService#getMonitor() getMonitor} should return the same.
*/
@Test
public void testSingleThread() {
TaskLauncher.launch(new Task("task1") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
TaskMonitor newMonitor = TaskMonitorService.getMonitor();
assertSame(newMonitor, monitor);
}
});
waitForTasks();
}
/**
* Verifies that in a single-threaded environment, the {@link TaskMonitorService}
* returns the same instance of {@link TaskMonitor} each time one is
* requested.
* <p>
* Note that the first time a monitor is requested it will always return the primary
* monitor that allows progress changes. Each subsequent time it will return the
* secondary monitor; these secondary monitors is what this test is verifying.
*/
@Test
public void testSingleThreadSecondaryMonitors() {
TaskLauncher.launch(new Task("task1") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
// First monitor requested is always the primary - just make the call so
// we can get to retrieving some secondary monitors
TaskMonitorService.getMonitor();
TaskMonitor secondaryMonitor1 = TaskMonitorService.getMonitor();
TaskMonitor secondaryMonitor2 = TaskMonitorService.getMonitor();
assertEquals(secondaryMonitor1, secondaryMonitor2);
}
});
waitForTasks();
}
/**
* Verifies that in a multi-threaded environment, the {@link TaskMonitorService}
* returns a different instance of {@link TaskMonitor} for each thread.
*/
@Test
public void testMultipleThreads() {
AtomicReference<TaskMonitor> localMonitor1 = new AtomicReference<>();
AtomicReference<TaskMonitor> localMonitor2 = new AtomicReference<>();
TaskLauncher.launch(new Task("task1") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
localMonitor1.set(TaskMonitorService.getMonitor());
}
});
TaskLauncher.launch(new Task("task2") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
localMonitor2.set(TaskMonitorService.getMonitor());
}
});
waitForTasks();
assertNotSame(localMonitor1.get(), localMonitor2.get());
}
/**
* Tests that if we try to register a monitor while on the Swing thread an exception will be
* thrown.
*/
@Test
public void testSetMonitorOnSwingThread() {
SystemUtilities.runSwingNow(() -> {
try {
TaskMonitorService.register(TaskMonitor.DUMMY);
}
catch (Exception e) {
// expected catch
return;
}
fail("Successful register of monitor on Swing thread (should not have been allowed)");
});
waitForTasks();
}
/**
* Verifies that if a client attempts to set a monitor on a thread that already has a monitor,
* an exception will be thrown.
* <p>
* Note: The first monitor is registered behind the scenes by the task launcher
*/
@Test
public void testRegisterMultipleMonitors() {
TaskLauncher.launch(new Task("task") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
try {
TaskMonitorService.register(TaskMonitor.DUMMY);
}
catch (Exception ex) {
// expected catch
return;
}
fail("Successful register of new monitor (should not have been allowed)");
}
});
waitForTasks();
}
/**
* Verifies that unique monitor id's are correctly assigned to each thread that registers
* a monitor
*/
@Test
public void testMonitorIds() {
AtomicInteger monitor1Id = new AtomicInteger();
AtomicInteger monitor2Id = new AtomicInteger();
AtomicInteger monitor3Id = new AtomicInteger();
Thread thread1 =
new Thread(() -> monitor1Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
Thread thread2 =
new Thread(() -> monitor2Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
Thread thread3 =
new Thread(() -> monitor3Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
thread1.start();
thread2.start();
thread3.start();
waitForTasks();
boolean areIdsDifferent =
(monitor1Id.get() != monitor2Id.get()) && (monitor2Id.get() != monitor3Id.get());
assertTrue(areIdsDifferent);
}
/**
* Verifies that if a client attempts to remove a monitor with an invalid id the request
* will fail
*/
@Test
public void testRemoveMonitorFail() {
AtomicBoolean removed = new AtomicBoolean(true);
// All monitor id's are positive integers; this is guaranteed to generated a failure
final int BOGUS_ID = -1;
Thread thread1 = new Thread(() -> {
TaskMonitorService.register(TaskMonitor.DUMMY);
try {
TaskMonitorService.remove(BOGUS_ID);
}
catch (Exception e) {
// expected catch
removed.set(false);
}
});
thread1.start();
waitForTasks();
assertFalse(removed.get());
}
/**
* Verifies that a monitor can be successfully removed given a correct id
*/
@Test
public void testRemoveMonitorSuccess() {
AtomicBoolean removed = new AtomicBoolean(false);
AtomicInteger monitorId = new AtomicInteger();
Thread thread1 = new Thread(() -> {
monitorId.set(TaskMonitorService.register(TaskMonitor.DUMMY));
try {
TaskMonitorService.remove(monitorId.get());
removed.set(true);
}
catch (Exception e) {
// should not be here
fail();
}
});
thread1.start();
waitForTasks();
assertTrue(removed.get());
}
/**
* Verifies that the first monitor returned from the service will be the
* primary monitor
*/
@Test
public void testRetrievePrimaryMonitor() {
TaskLauncher.launch(new Task("task") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
assertTrue(monitor1 instanceof TaskDialog);
}
});
waitForTasks();
}
/**
* Verifies that any subsequent requests after the initial request will result in
* a {@link SecondaryTaskDialog} being returned
*/
@Test
public void testRetrieveSecondaryMonitor() {
TaskLauncher.launch(new Task("task") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
assertTrue(monitor1 instanceof TaskDialog);
monitor1 = TaskMonitorService.getMonitor();
assertTrue(monitor1 instanceof SecondaryTaskMonitor);
}
});
waitForTasks();
}
/**
* Verifies that once a monitor has been reset, the next call to retrieve a monitor will
* return it as a primary
*/
@Test
public void testMonitorReset() {
TaskLauncher.launch(new Task("task") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
assertTrue(monitor1 instanceof TaskDialog);
monitor1 = TaskMonitorService.getMonitor();
assertTrue(monitor1 instanceof SecondaryTaskMonitor);
monitor1.reset();
monitor1 = TaskMonitorService.getMonitor();
assertTrue(monitor1 instanceof TaskDialog);
}
});
waitForTasks();
}
}

View file

@ -67,6 +67,7 @@ public class DialogComponentProvider
protected JButton dismissButton; protected JButton dismissButton;
private boolean isAlerting; private boolean isAlerting;
private JLabel statusLabel; private JLabel statusLabel;
private JLabel subStatusLabel;
private JPanel statusProgPanel; // contains status panel and progress panel private JPanel statusProgPanel; // contains status panel and progress panel
private Timer showTimer; private Timer showTimer;
private TaskScheduler taskScheduler; private TaskScheduler taskScheduler;
@ -609,6 +610,11 @@ public class DialogComponentProvider
setStatusText(text, MessageType.INFO); setStatusText(text, MessageType.INFO);
} }
@Override
public void setSubStatusText(String text) {
setSubStatusText(text, MessageType.INFO);
}
/** /**
* Sets the text in the dialog's status line using the specified message type to control * Sets the text in the dialog's status line using the specified message type to control
* the color. * the color.
@ -621,6 +627,11 @@ public class DialogComponentProvider
setStatusText(message, type, false); setStatusText(message, type, false);
} }
@Override
public void setSubStatusText(String message, MessageType type) {
setSubStatusText(message, type, false);
}
@Override @Override
public void setStatusText(String message, MessageType type, boolean alert) { public void setStatusText(String message, MessageType type, boolean alert) {
@ -628,6 +639,13 @@ public class DialogComponentProvider
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetStatusText(text, type, alert)); SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetStatusText(text, type, alert));
} }
@Override
public void setSubStatusText(String message, MessageType type, boolean alert) {
String text = StringUtils.isBlank(message) ? " " : message;
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetSubStatusText(text, type, alert));
}
private void doSetStatusText(String text, MessageType type, boolean alert) { private void doSetStatusText(String text, MessageType type, boolean alert) {
SystemUtilities.assertThisIsTheSwingThread( SystemUtilities.assertThisIsTheSwingThread(
@ -642,6 +660,20 @@ public class DialogComponentProvider
} }
} }
private void doSetSubStatusText(String text, MessageType type, boolean alert) {
SystemUtilities.assertThisIsTheSwingThread(
"Setting text must be performed on the Swing thread");
subStatusLabel.setText(text);
subStatusLabel.setForeground(getStatusColor(type));
updateStatusToolTip();
if (alert) {
alertMessage();
}
}
/** /**
* Signals for this dialog to visually draw the user's attention to the status text * Signals for this dialog to visually draw the user's attention to the status text
*/ */
@ -679,11 +711,13 @@ public class DialogComponentProvider
// normal Swing mechanism may not have yet happened). // normal Swing mechanism may not have yet happened).
mainPanel.validate(); mainPanel.validate();
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
subStatusLabel.setVisible(false);
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1); Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
animator.addTarget(new TimingTargetAdapter() { animator.addTarget(new TimingTargetAdapter() {
@Override @Override
public void end() { public void end() {
statusLabel.setVisible(true); statusLabel.setVisible(true);
subStatusLabel.setVisible(true);
alertFinishedCallback.call(); alertFinishedCallback.call();
isAlerting = false; isAlerting = false;
} }
@ -755,12 +789,7 @@ public class DialogComponentProvider
private void showProgressBar(String localTitle, boolean hasProgress, boolean canCancel) { private void showProgressBar(String localTitle, boolean hasProgress, boolean canCancel) {
taskMonitorComponent.setTaskName(localTitle); taskMonitorComponent.setTaskName(localTitle);
taskMonitorComponent.showProgress(hasProgress); taskMonitorComponent.showProgress(hasProgress);
if (canCancel) { taskMonitorComponent.setCancelButtonVisibility(canCancel);
taskMonitorComponent.showCancelButton(true);
}
else {
taskMonitorComponent.showCancelButton(false);
}
progressCardLayout.show(statusProgPanel, PROGRESS); progressCardLayout.show(statusProgPanel, PROGRESS);
rootPanel.validate(); rootPanel.validate();
} }
@ -799,18 +828,29 @@ public class DialogComponentProvider
public void clearStatusText() { public void clearStatusText() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> { SystemUtilities.runIfSwingOrPostSwingLater(() -> {
statusLabel.setText(" "); statusLabel.setText(" ");
subStatusLabel.setText(" ");
updateStatusToolTip(); updateStatusToolTip();
}); });
} }
/** /**
* returns the current status in the dialogs status line= * Returns the current status in the dialogs status line
*
* @return the status text * @return the status text
*/ */
public String getStatusText() { public String getStatusText() {
return statusLabel.getText(); return statusLabel.getText();
} }
/**
* Returns the secondary status message
*
* @return the secondary status message
*/
public String getSubStatusText() {
return subStatusLabel.getText();
}
protected JLabel getStatusLabel() { protected JLabel getStatusLabel() {
return statusLabel; return statusLabel;
} }
@ -902,12 +942,21 @@ public class DialogComponentProvider
} }
}); });
subStatusLabel = new JLabel();
subStatusLabel.setName("subStatusLabel");
subStatusLabel.setHorizontalAlignment(SwingConstants.CENTER);
subStatusLabel.setForeground(Color.blue);
subStatusLabel.setFont(subStatusLabel.getFont().deriveFont(Font.ITALIC));
subStatusLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
subStatusLabel.setFont(subStatusLabel.getFont().deriveFont(11.0f));
// use a strut panel so the size of the message area does not change if we make // use a strut panel so the size of the message area does not change if we make
// the message label not visible // the message label not visible
int height = statusLabel.getPreferredSize().height; int height = statusLabel.getPreferredSize().height;
panel.add(Box.createVerticalStrut(height), BorderLayout.WEST); panel.add(Box.createVerticalStrut(height), BorderLayout.WEST);
panel.add(statusLabel, BorderLayout.CENTER); panel.add(statusLabel, BorderLayout.CENTER);
panel.add(subStatusLabel, BorderLayout.SOUTH);
return panel; return panel;
} }

View file

@ -0,0 +1,48 @@
/* ###
* 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.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import docking.util.AnimatedIcon;
import resources.ResourceManager;
/**
* Panel that displays an animation of the Ghidra dragon chomping bits.
*/
public class ChompingBitsAnimationPanel extends JPanel {
public ChompingBitsAnimationPanel() {
setLayout(new BorderLayout());
List<Icon> iconList = new ArrayList<>();
iconList.add(ResourceManager.loadImage("images/eatbits1.png"));
iconList.add(ResourceManager.loadImage("images/eatbits2.png"));
iconList.add(ResourceManager.loadImage("images/eatbits3.png"));
iconList.add(ResourceManager.loadImage("images/eatbits4.png"));
iconList.add(ResourceManager.loadImage("images/eatbits5.png"));
iconList.add(ResourceManager.loadImage("images/eatbits6.png"));
iconList.add(ResourceManager.loadImage("images/eatbits7.png"));
AnimatedIcon icon = new AnimatedIcon(iconList, 200, 0);
setSize(new Dimension(200, 100));
add(new JLabel(icon));
}
}

View file

@ -0,0 +1,59 @@
/* ###
* 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.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import docking.util.AnimatedIcon;
import resources.ResourceManager;
/**
* Panel that displays an animation of a spinning hourglass
*/
public class HourglassAnimationPanel extends JPanel {
public HourglassAnimationPanel() {
setLayout(new BorderLayout());
List<Icon> iconList = new ArrayList<>();
iconList.add(ResourceManager.loadImage("images/hourglass24_01.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_02.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_02.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_03.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_03.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_04.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_04.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_05.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_05.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_06.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_06.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_07.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_07.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_08.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_08.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_09.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_10.png"));
iconList.add(ResourceManager.loadImage("images/hourglass24_11.png"));
AnimatedIcon progressIcon = new AnimatedIcon(iconList, 150, 0);
add (new JLabel(progressIcon), BorderLayout.CENTER);
}
}

View file

@ -245,7 +245,7 @@ public class RunManager {
* @param showCancel true means to show the cancel button * @param showCancel true means to show the cancel button
*/ */
public void showCancelButton(boolean showCancel) { public void showCancelButton(boolean showCancel) {
monitor.showCancelButton(showCancel); monitor.setCancelButtonVisibility(showCancel);
} }
/** /**

View file

@ -0,0 +1,168 @@
/* ###
* 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.Objects;
import ghidra.util.exception.CancelledException;
/**
* {@link TaskMonitor} that restricts users from being able to update the progress bar. The class
* is initialized with another, fully-featured monitor and forwards all requests to it,
* but squashes calls to methods that are not allowed.
* <p>
* Note: Two instances of this class are deemed equal if they have the same {@link #parentMonitor},
* hence the override of {@link #hashCode()} and {@link #equals(Object)}.
*/
public class SecondaryTaskMonitor implements TaskMonitor {
private TaskMonitor parentMonitor;
/**
* Constructor
*
* @param parentMonitor the fully-functional task monitor this is based off of
*/
public SecondaryTaskMonitor(TaskMonitor parentMonitor) {
this.parentMonitor = parentMonitor;
}
/**
* Overridden to ensure that clients who have this type of monitor will only update the
* secondary message when using this method
*
* @param message the message string to display
*/
@Override
public void setMessage(String message) {
if (parentMonitor instanceof TaskDialog) {
((TaskDialog) parentMonitor).setSecondaryMessage(message);
}
parentMonitor.setMessage(message);
}
@Override
public void setCancelEnabled(boolean enable) {
parentMonitor.setCancelEnabled(enable);
}
@Override
public void setInitialized(boolean init) {
parentMonitor.setInitialized(init);
}
@Override
public boolean isCancelEnabled() {
return parentMonitor.isCancelEnabled();
}
@Override
public boolean isCancelled() {
return parentMonitor.isCancelled();
}
@Override
public synchronized void cancel() {
parentMonitor.cancel();
}
@Override
public synchronized void clearCanceled() {
parentMonitor.clearCanceled();
}
@Override
public void checkCanceled() throws CancelledException {
parentMonitor.checkCanceled();
}
@Override
public void addCancelledListener(CancelledListener listener) {
parentMonitor.addCancelledListener(listener);
}
@Override
public void removeCancelledListener(CancelledListener listener) {
parentMonitor.removeCancelledListener(listener);
}
@Override
public long getMaximum() {
return parentMonitor.getMaximum();
}
@Override
public long getProgress() {
return parentMonitor.getProgress();
}
@Override
public void setShowProgressValue(boolean showProgressValue) {
// squash
}
@Override
public void setProgress(long value) {
// squash
}
@Override
public void initialize(long max) {
// squash
}
@Override
public void setMaximum(long max) {
// squash
}
@Override
public void setIndeterminate(boolean indeterminate) {
// squash
}
@Override
public void incrementProgress(long incrementAmount) {
// squash
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((parentMonitor == null) ? 0 : parentMonitor.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
SecondaryTaskMonitor other = (SecondaryTaskMonitor) obj;
if (!Objects.equals(parentMonitor, other.parentMonitor)) {
return false;
}
return true;
}
}

View file

@ -15,20 +15,20 @@
*/ */
package ghidra.util.task; package ghidra.util.task;
import java.awt.*; import java.awt.BorderLayout;
import java.awt.Component;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*; import javax.swing.*;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.util.AnimatedIcon;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.label.GIconLabel; import ghidra.util.HelpLocation;
import ghidra.util.*; import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.timer.GTimer; import ghidra.util.timer.GTimer;
import resources.ResourceManager;
/** /**
* Dialog that is displayed to show activity for a Task that is running outside of the * Dialog that is displayed to show activity for a Task that is running outside of the
@ -36,25 +36,48 @@ import resources.ResourceManager;
*/ */
public class TaskDialog extends DialogComponentProvider implements TaskMonitor { public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
/** Timer used to give the task a chance to complete */
private static final int SLEEPY_TIME = 10; private static final int SLEEPY_TIME = 10;
/** Amount of time to wait before showing the monitor dialog */
private final static int MAX_DELAY = 200000; private final static int MAX_DELAY = 200000;
public final static int DEFAULT_WIDTH = 275; public final static int DEFAULT_WIDTH = 275;
private Timer showTimer; private Timer showTimer;
private TaskMonitorComponent monitorComponent;
private AtomicInteger taskID = new AtomicInteger(); private AtomicInteger taskID = new AtomicInteger();
private boolean canCancel;
private Runnable updateMessage;
private Runnable closeDialog; private Runnable closeDialog;
private Runnable enableCancelButton;
private String newMessage;
private boolean cancelState = true;
private Component centerOnComp; private Component centerOnComp;
private Runnable shouldCancelRunnable; private Runnable shouldCancelRunnable;
private boolean done; private boolean taskDone;
private JPanel mainPanel; private JPanel mainPanel;
private ChompingBitsAnimationPanel chompingBitsPanel;
private TaskMonitorComponent monitorComponent;
/** Creates new TaskDialog /** Runnable that updates the primary message label in the dialog */
private Runnable updatePrimaryMessageRunnable;
/** Runnable that updates the secondary message label in the dialog */
private Runnable updateSecondaryMessageRunnable;
/** If not null, then the value of the string has yet to be rendered */
private String newPrimaryMessage;
/** If not null, then the value of the string has yet to be rendered */
private String newSecondaryMessage;
/**
* Indicates if this monitor has been initialized for progress updates. If this value
* is set to true, the {@link TaskMonitorService} will not return the monitor to
* another caller (only one client should be able to update progress at a time).
*/
private AtomicBoolean initialized = new AtomicBoolean(false);
private SecondaryTaskMonitor secondaryTaskMonitor;
/**
* Constructor
*
* @param centerOnComp component to be centered over when shown, * @param centerOnComp component to be centered over when shown,
* otherwise center over parent. If both centerOnComp and parent * otherwise center over parent. If both centerOnComp and parent
* are null, dialog will be centered on screen. * are null, dialog will be centered on screen.
@ -65,7 +88,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
task.hasProgress()); task.hasProgress());
} }
/** Creates a new TaskDialog. /**
* Constructor
*
* @param task the Task that this dialog will be associated with * @param task the Task that this dialog will be associated with
*/ */
public TaskDialog(Task task) { public TaskDialog(Task task) {
@ -73,18 +98,20 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
} }
/** /**
* Construct new TaskDialog. * Constructor
*
* @param title title for the dialog * @param title title for the dialog
* @param canCancel true if the task can be canceled * @param canCancel true if the task can be canceled
* @param isModal true if the dialog should be modal * @param isModal true if the dialog should be modal
* @param hasProgress true if the dialog should show a progress bar * @param hasProgress true if the dialog should show a progress bar
*/ */
public TaskDialog(String title, boolean canCancel, boolean isModal, boolean hasProgress) { public TaskDialog(String title, boolean canCancel, boolean isModal, boolean hasProgress) {
this(null, title, isModal, canCancel, true /*hasProgress*/); this(null, title, isModal, canCancel, hasProgress);
} }
/** /**
* Construct new TaskDialog. * Constructor
*
* @param centerOnComp component to be centered over when shown, otherwise center over * @param centerOnComp component to be centered over when shown, otherwise center over
* parent. If both centerOnComp is null, then the active window will be used * parent. If both centerOnComp is null, then the active window will be used
* @param title title for the dialog * @param title title for the dialog
@ -99,8 +126,21 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
setup(canCancel, hasProgress); setup(canCancel, hasProgress);
} }
@Override
public boolean isInitialized() {
return initialized.get();
}
@Override
public void setInitialized(boolean init) {
this.initialized.set(init);
}
private void setup(boolean canCancel, boolean hasProgress) { private void setup(boolean canCancel, boolean hasProgress) {
this.canCancel = canCancel; monitorComponent = new TaskMonitorComponent(false, false);
chompingBitsPanel = new ChompingBitsAnimationPanel();
setCancelEnabled(canCancel);
setRememberLocation(false); setRememberLocation(false);
setRememberSize(false); setRememberSize(false);
setTransient(true); setTransient(true);
@ -108,13 +148,18 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
close(); close();
dispose(); dispose();
}; };
updateMessage = () -> { updatePrimaryMessageRunnable = () -> {
setStatusText(newMessage); setStatusText(newPrimaryMessage);
synchronized (TaskDialog.this) { synchronized (TaskDialog.this) {
newMessage = null; newPrimaryMessage = null;
}
};
updateSecondaryMessageRunnable = () -> {
setSubStatusText(newSecondaryMessage);
synchronized (TaskDialog.this) {
newSecondaryMessage = null;
} }
}; };
enableCancelButton = () -> TaskDialog.super.setCancelEnabled(cancelState);
shouldCancelRunnable = () -> { shouldCancelRunnable = () -> {
int currentTaskID = taskID.get(); int currentTaskID = taskID.get();
@ -124,9 +169,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
} }
}; };
monitorComponent = new TaskMonitorComponent(false, false);
mainPanel = new JPanel(new BorderLayout()); mainPanel = new JPanel(new BorderLayout());
addWorkPanel(mainPanel); addWorkPanel(mainPanel);
if (hasProgress) { if (hasProgress) {
installProgressMonitor(); installProgressMonitor();
} }
@ -142,6 +187,11 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
setHelpLocation(new HelpLocation("Tool", "TaskDialog")); setHelpLocation(new HelpLocation("Tool", "TaskDialog"));
} }
/**
* Shows a dialog asking the user if they really, really want to cancel the task
*
* @return true if the task should be cancelled
*/
protected boolean promptToVerifyCancel() { protected boolean promptToVerifyCancel() {
boolean userSaysYes = OptionDialog.showYesNoDialog(getComponent(), "Cancel?", boolean userSaysYes = OptionDialog.showYesNoDialog(getComponent(), "Cancel?",
"Do you really want to cancel \"" + getTitle() + "\"?") == OptionDialog.OPTION_ONE; "Do you really want to cancel \"" + getTitle() + "\"?") == OptionDialog.OPTION_ONE;
@ -150,34 +200,24 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
} }
/** /**
* Creates the main work panel for the dialog * Adds the panel that contains the progress bar to the dialog
*/ */
private void installProgressMonitor() { private void installProgressMonitor() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> { SystemUtilities.runIfSwingOrPostSwingLater(() -> {
mainPanel.removeAll(); mainPanel.removeAll();
mainPanel.add(monitorComponent, BorderLayout.CENTER);
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 10, 5, 10));
panel.add(monitorComponent);
mainPanel.add(panel, BorderLayout.NORTH);
repack(); repack();
}); });
} }
/**
* Adds the panel that contains the chomping bits animation to the dialog. This should only be
* called if the dialog has no need to display progress.
*/
private void installActivityDisplay() { private void installActivityDisplay() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> { SystemUtilities.runIfSwingOrPostSwingLater(() -> {
mainPanel.removeAll(); mainPanel.removeAll();
mainPanel.add(chompingBitsPanel, BorderLayout.CENTER);
JPanel panel = new JPanel(new BorderLayout());
panel.setSize(new Dimension(200, 100));
String[] filenames = { "images/eatbits1.png", "images/eatbits2.png",
"images/eatbits3.png", "images/eatbits4.png", "images/eatbits5.png",
"images/eatbits6.png", "images/eatbits7.png" };
panel.add(
new GIconLabel(new AnimatedIcon(ResourceManager.loadImages(filenames), 200, 0)));
mainPanel.add(panel, BorderLayout.CENTER);
repack(); repack();
}); });
} }
@ -200,7 +240,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
monitorComponent.setShowProgressValue(showProgressValue); monitorComponent.setShowProgressValue(showProgressValue);
} }
/** Sets the percentage done. /**
* Sets the percentage done
*
* @param param The percentage of the task completed. * @param param The percentage of the task completed.
*/ */
@Override @Override
@ -250,62 +292,58 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
monitorComponent.setIndeterminate(indeterminate); monitorComponent.setIndeterminate(indeterminate);
} }
/** Called if the user presses the cancel button on
* the dialog
*/
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
synchronized (this) {
if (!monitorComponent.isCancelEnabled() || monitorComponent.isCancelled()) {
return;
}
}
SwingUtilities.invokeLater(shouldCancelRunnable); SwingUtilities.invokeLater(shouldCancelRunnable);
} }
/** Sets the message in the TaskDialog dialog
* @param str The message string to be displayed
*/
@Override @Override
synchronized public void setMessage(String str) { synchronized public void setMessage(String str) {
boolean invoke = (newMessage == null); boolean invoke = (newPrimaryMessage == null);
newMessage = str;
if (invoke) { if (invoke) {
SwingUtilities.invokeLater(updateMessage); newPrimaryMessage = str;
SwingUtilities.invokeLater(updatePrimaryMessageRunnable);
} }
} }
/** /**
* Set the enable state of the Cancel button. * Updates the secondary message on the task monitor
* @param enable the state to set the cancel button. *
* @param str the string to update
*/ */
synchronized public void setSecondaryMessage(String str) {
boolean invoke = (newSecondaryMessage == null);
if (invoke) {
newSecondaryMessage = str;
SwingUtilities.invokeLater(updateSecondaryMessageRunnable);
}
}
@Override @Override
public void setCancelEnabled(boolean enable) { public void setCancelEnabled(boolean enable) {
if (canCancel) {
monitorComponent.setCancelEnabled(enable); monitorComponent.setCancelEnabled(enable);
SwingUtilities.invokeLater(enableCancelButton); super.setCancelEnabled(enable);
}
} }
@Override @Override
public boolean isCancelEnabled() { public boolean isCancelEnabled() {
return canCancel && cancelState; return super.isCancelEnabled();
} }
public synchronized void taskProcessed() { public synchronized void taskProcessed() {
done = true; taskDone = true;
monitorComponent.notifyChangeListeners(); monitorComponent.notifyChangeListeners();
SwingUtilities.invokeLater(closeDialog); SwingUtilities.invokeLater(closeDialog);
} }
@Override
public synchronized void reset() { public synchronized void reset() {
done = false; taskDone = false;
taskID.incrementAndGet(); taskID.incrementAndGet();
} }
public synchronized boolean isCompleted() { public synchronized boolean isCompleted() {
return done; return taskDone;
} }
@Override @Override
@ -393,7 +431,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
@Override @Override
public synchronized void cancel() { public synchronized void cancel() {
if (!canCancel || monitorComponent.isCancelled()) { if (monitorComponent.isCancelled()) {
return; return;
} }
// Mark as cancelled, must be detected by task which should terminate // Mark as cancelled, must be detected by task which should terminate
@ -432,18 +470,11 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
} }
@Override @Override
public void addIssueListener(IssueListener listener) { public synchronized TaskMonitor getSecondaryMonitor() {
monitorComponent.addIssueListener(listener); if (secondaryTaskMonitor == null) {
secondaryTaskMonitor = new SecondaryTaskMonitor(this);
}
return secondaryTaskMonitor;
} }
@Override
public void removeIssueListener(IssueListener listener) {
monitorComponent.removeIssueListener(listener);
}
@Override
public void reportIssue(Issue issue) {
monitorComponent.reportIssue(issue);
}
} }

View file

@ -24,75 +24,79 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*; import javax.swing.*;
import docking.ToolTipManager; import docking.ToolTipManager;
import docking.util.AnimatedIcon;
import docking.widgets.EmptyBorderButton; import docking.widgets.EmptyBorderButton;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.label.GDHtmlLabel; import docking.widgets.label.GDHtmlLabel;
import docking.widgets.label.GIconLabel;
import ghidra.util.Issue;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet; import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.layout.VerticalLayout; import ghidra.util.layout.VerticalLayout;
import resources.Icons; import resources.Icons;
import resources.ResourceManager;
/** /**
* Component that contains a progress bar, a progress icon, and a cancel * Component that contains a progress bar, a progress icon, and a cancel
* button to cancel the task that is associated with this task monitor. * button to cancel the task that is associated with this task monitor.
* <p>
* By default the progress bar and progress icon (spinning globe) are visible. * By default the progress bar and progress icon (spinning globe) are visible.
*/ */
public class TaskMonitorComponent extends JPanel implements TaskMonitor { public class TaskMonitorComponent extends JPanel implements TaskMonitor {
private WeakSet<CancelledListener> listeners = private WeakSet<CancelledListener> listeners =
WeakDataStructureFactory.createCopyOnReadWeakSet(); WeakDataStructureFactory.createCopyOnReadWeakSet();
private WeakSet<IssueListener> issueListeners;
private JButton cancelButton;
private JPanel eastButtonPanel;
private JProgressBar progressBar;
private JPanel progressPanel;
private JPanel activeProgressPanel;
private JLabel imageLabel;
private JProgressBar progressBar;
private JButton cancelButton;
private JPanel cancelPanel;
private JPanel progressBarPanel;
private JPanel mainContentPanel;
private JPanel progressPanel;
private String progressMessage;
private String taskName; private String taskName;
private JLabel messageLabel;
private volatile boolean isCancelled; private volatile boolean isCancelled;
private String message;
private long lastProgress = -1; private long lastProgress = -1;
private long progress; private long progress;
private long lastMax = -1; private long lastMaxProgress = -1;
private long max; private long maxProgress;
private long scaleFactor = 1;
private Runnable updateProgressPanelRunnable; private Runnable updateProgressPanelRunnable;
private Runnable updateCancelButtonRunnable; private Runnable updateCancelButtonRunnable;
private Runnable updateToolTipRunnable; private Runnable updateToolTipRunnable;
private JLabel messageLabel; private Runnable shouldCancelRunnable;
private boolean showingProgress = true; private boolean showingProgress = true;
private boolean showingIcon = true; private boolean showingIcon = true;
private boolean showingCancelButton = true; private boolean showingCancelButton = true;
private boolean cancelEnabled = true; private boolean cancelEnabled = true;
private boolean paintProgressValue = true;
private AtomicBoolean isIndeterminate = new AtomicBoolean(false); private AtomicBoolean isIndeterminate = new AtomicBoolean(false);
private AtomicInteger taskID = new AtomicInteger(); private AtomicInteger taskID = new AtomicInteger();
private Timer updateTimer; private Timer updateTimer;
private Runnable shouldCancelRunnable;
private boolean paintProgressValue = true;
private NumberFormat percentFormat = NumberFormat.getPercentInstance(); private NumberFormat percentFormat = NumberFormat.getPercentInstance();
private long scaleFactor = 1;
/** /**
* Construct a new TaskMonitorComponent. * Constructor
* @param l listener that is notified when the task completes or the
* user cancels the task
*/ */
public TaskMonitorComponent() { public TaskMonitorComponent() {
this(true, true); this(true, true);
} }
/**
* Constructor
*
* @param includeTextField if true, the dialog can display a status progressMessage with progress details
* @param includeCancelButton if true, a cancel button will be displayed
*/
public TaskMonitorComponent(boolean includeTextField, boolean includeCancelButton) { public TaskMonitorComponent(boolean includeTextField, boolean includeCancelButton) {
updateProgressPanelRunnable = () -> updateProgressPanel(); updateProgressPanelRunnable = () -> updateProgressPanel();
updateCancelButtonRunnable = () -> updateCancelButton(); updateCancelButtonRunnable = () -> updateCancelButton();
@ -114,12 +118,24 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
buildProgressPanel(includeTextField, includeCancelButton); buildProgressPanel(includeTextField, includeCancelButton);
} }
/** @Override
* Reset this monitor so that it can be reused. public void addCancelledListener(CancelledListener mcl) {
*/ listeners.add(mcl);
public synchronized void reset() { }
isCancelled = false;
taskID.incrementAndGet(); @Override
public void removeCancelledListener(CancelledListener mcl) {
listeners.remove(mcl);
}
@Override
public void incrementProgress(long incrementAmount) {
setProgress(progress + incrementAmount);
}
@Override
public long getProgress() {
return progress;
} }
@Override @Override
@ -136,7 +152,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
@Override @Override
public synchronized void setMessage(String message) { public synchronized void setMessage(String message) {
this.message = message; this.progressMessage = message;
startUpdateTimer(); startUpdateTimer();
} }
@ -149,12 +165,6 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
startUpdateTimer(); startUpdateTimer();
} }
private synchronized void startUpdateTimer() {
if (!updateTimer.isRunning()) {
updateTimer.start();
}
}
@Override @Override
public void initialize(long maxValue) { public void initialize(long maxValue) {
setMaximum(maxValue); setMaximum(maxValue);
@ -163,8 +173,8 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
@Override @Override
public void setMaximum(long max) { public void setMaximum(long max) {
this.max = max; this.maxProgress = max;
if (progress > this.max) { if (progress > this.maxProgress) {
progress = max; progress = max;
} }
startUpdateTimer(); startUpdateTimer();
@ -201,17 +211,6 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
}); });
} }
/**
* Returns true if {@link #setIndeterminate(boolean)} with a value of <tt>true</tt> has
* been called.
*
* @return true if {@link #setIndeterminate(boolean)} with a value of <tt>true</tt> has
* been called.
*/
public boolean isIndeterminate() {
return isIndeterminate.get();
}
@Override @Override
public synchronized void setCancelEnabled(boolean enable) { public synchronized void setCancelEnabled(boolean enable) {
if (cancelEnabled != enable) { if (cancelEnabled != enable) {
@ -244,66 +243,6 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
} }
} }
/**
* Set whether the progress bar should be visible.
* @param b true if the progress bar should be visible
*/
public synchronized void showProgress(boolean b) {
if (b != showingProgress) {
showingProgress = b;
SystemUtilities.runSwingLater(updateProgressPanelRunnable);
}
}
/**
* Set the name of the task; the name shows up in the tool tip for
* the cancel button.
* @param name the name of the task
*/
public void setTaskName(String name) {
taskName = name;
SystemUtilities.runSwingLater(updateToolTipRunnable);
}
/**
* Show or not show the cancel button according to the showCancel param.
*/
public void showCancelButton(boolean showCancel) {
if (showCancel == showingCancelButton) {
return;
}
if (showCancel) {
add(eastButtonPanel, BorderLayout.EAST);
}
else {
remove(eastButtonPanel);
}
showingCancelButton = showCancel;
}
/**
* Show or not show the progress icon (spinning globe) according to
* the showIcon param.
*/
public void showProgressIcon(final boolean showIcon) {
if (showIcon == showingIcon) {
return;
}
Runnable r = () -> {
if (showIcon) {
activeProgressPanel.add(imageLabel, BorderLayout.EAST);
}
else {
activeProgressPanel.remove(imageLabel);
}
showingIcon = showIcon;
};
SystemUtilities.runSwingNow(r);
}
@Override @Override
public void setShowProgressValue(boolean showProgressValue) { public void setShowProgressValue(boolean showProgressValue) {
this.paintProgressValue = showProgressValue; this.paintProgressValue = showProgressValue;
@ -312,19 +251,121 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
@Override @Override
public long getMaximum() { public long getMaximum() {
return max; return maxProgress;
}
/**
* Reset this monitor so that it can be reused
*/
@Override
public synchronized void reset() {
isCancelled = false;
taskID.incrementAndGet();
}
/**
* Returns true if {@link #setIndeterminate(boolean)} with a value of <tt>true</tt> has
* been called.
*
* @return true if {@link #setIndeterminate(boolean)} with a value of <tt>true</tt> has
* been called.
*/
public boolean isIndeterminate() {
return isIndeterminate.get();
}
/**
* Set whether the progress bar should be visible
*
* @param show true if the progress bar should be visible
*/
public synchronized void showProgress(boolean show) {
if (show != showingProgress) {
showingProgress = show;
SystemUtilities.runSwingLater(updateProgressPanelRunnable);
}
}
/**
* Set the name of the task; the name shows up in the tool tip for
* the cancel button.
*
* @param name the name of the task
*/
public void setTaskName(String name) {
taskName = name;
SystemUtilities.runSwingLater(updateToolTipRunnable);
}
/**
* Set the visibility of the cancel button
*
* @param visible if true, show the cancel button; false otherwise
*/
public void setCancelButtonVisibility(boolean visible) {
if (visible == showingCancelButton) {
return;
}
if (visible) {
add(cancelPanel, BorderLayout.EAST);
}
else {
remove(cancelPanel);
}
repaint();
showingCancelButton = visible;
}
/**
* Sets the visibility of the progress icon
*
* @param visible if true, display the progress icon
*/
public void showProgressIcon(boolean visible) {
if (visible == showingIcon) {
return;
}
Runnable r = () -> {
if (visible) {
mainContentPanel.add(progressPanel, BorderLayout.EAST);
}
else {
mainContentPanel.remove(progressPanel);
}
showingIcon = visible;
};
SystemUtilities.runSwingNow(r);
}
protected void notifyChangeListeners() {
Runnable r = () -> {
for (CancelledListener mcl : listeners) {
mcl.cancelled();
}
};
SwingUtilities.invokeLater(r);
}
private synchronized void startUpdateTimer() {
if (!updateTimer.isRunning()) {
updateTimer.start();
}
} }
private synchronized void update() { private synchronized void update() {
if (message != null) { if (progressMessage != null) {
messageLabel.setText(message); messageLabel.setText(progressMessage);
message = null; progressMessage = null;
} }
if (max != lastMax) { if (maxProgress != lastMaxProgress) {
setMaxValueInProgressBar(max); setMaxValueInProgressBar(maxProgress);
lastMax = max; lastMaxProgress = maxProgress;
} }
if (progress != lastProgress) { if (progress != lastProgress) {
@ -379,10 +420,10 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
private synchronized void updateProgressPanel() { private synchronized void updateProgressPanel() {
if (showingProgress) { if (showingProgress) {
progressPanel.add(progressBar, BorderLayout.NORTH); progressBarPanel.add(progressBar, BorderLayout.NORTH);
} }
else { else {
progressPanel.remove(progressBar); progressBarPanel.remove(progressBar);
} }
} }
@ -424,24 +465,24 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
progressBar.setStringPainted(true); progressBar.setStringPainted(true);
ToolTipManager.sharedInstance().registerComponent(progressBar); ToolTipManager.sharedInstance().registerComponent(progressBar);
createAnimatedIcon(); progressPanel = new HourglassAnimationPanel();
progressPanel = new JPanel(new VerticalLayout(0)); progressBarPanel = new JPanel(new VerticalLayout(0));
progressPanel.add(progressBar); progressBarPanel.add(progressBar);
if (includeTextField) { if (includeTextField) {
progressPanel.add(messageLabel); progressBarPanel.add(messageLabel);
progressBar.setPreferredSize(new Dimension(180, 12)); progressBar.setPreferredSize(new Dimension(180, 12));
} }
else { else {
progressBar.setBorderPainted(true); progressBar.setBorderPainted(true);
Dimension size = progressBar.getPreferredSize(); Dimension size = progressBar.getPreferredSize();
progressPanel.setBorder(BorderFactory.createEmptyBorder( progressBarPanel.setBorder(BorderFactory.createEmptyBorder(
(imageLabel.getPreferredSize().height - size.height) / 2, 0, 0, 8)); (progressPanel.getPreferredSize().height - size.height) / 2, 0, 0, 8));
} }
activeProgressPanel = new JPanel(new BorderLayout()); mainContentPanel = new JPanel(new BorderLayout());
activeProgressPanel.add(progressPanel, BorderLayout.CENTER); mainContentPanel.add(progressBarPanel, BorderLayout.CENTER);
activeProgressPanel.add(imageLabel, BorderLayout.EAST); mainContentPanel.add(progressPanel, BorderLayout.EAST);
ImageIcon icon = Icons.STOP_ICON; ImageIcon icon = Icons.STOP_ICON;
cancelButton = new EmptyBorderButton(icon); cancelButton = new EmptyBorderButton(icon);
@ -452,86 +493,15 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
cancelButton.setFocusable(false); cancelButton.setFocusable(false);
cancelButton.setRolloverEnabled(true); cancelButton.setRolloverEnabled(true);
add(activeProgressPanel, BorderLayout.CENTER); add(mainContentPanel, BorderLayout.CENTER);
if (includeCancelButton) { if (includeCancelButton) {
eastButtonPanel = new JPanel(); cancelPanel = new JPanel();
eastButtonPanel.setLayout(new BoxLayout(eastButtonPanel, BoxLayout.Y_AXIS)); cancelPanel.setLayout(new BoxLayout(cancelPanel, BoxLayout.Y_AXIS));
eastButtonPanel.add(Box.createVerticalGlue()); cancelPanel.add(Box.createVerticalGlue());
eastButtonPanel.add(cancelButton); cancelPanel.add(cancelButton);
eastButtonPanel.add(Box.createVerticalGlue()); cancelPanel.add(Box.createVerticalGlue());
add(eastButtonPanel, BorderLayout.EAST); add(cancelPanel, BorderLayout.EAST);
}
}
private void createAnimatedIcon() {
String[] filenames = { "images/hourglass24_01.png", "images/hourglass24_02.png",
"images/hourglass24_02.png", "images/hourglass24_03.png", "images/hourglass24_03.png",
"images/hourglass24_04.png", "images/hourglass24_04.png", "images/hourglass24_05.png",
"images/hourglass24_05.png", "images/hourglass24_06.png", "images/hourglass24_06.png",
"images/hourglass24_07.png", "images/hourglass24_07.png", "images/hourglass24_08.png",
"images/hourglass24_08.png", "images/hourglass24_09.png", "images/hourglass24_10.png",
"images/hourglass24_11.png" };
imageLabel =
new GIconLabel(new AnimatedIcon(ResourceManager.loadImages(filenames), 150, 0));
}
@Override
public void incrementProgress(long incrementAmount) {
setProgress(progress + incrementAmount);
}
@Override
public long getProgress() {
return progress;
}
protected void notifyChangeListeners() {
Runnable r = () -> {
synchronized (listeners) {
for (CancelledListener mcl : listeners) {
mcl.cancelled();
}
}
};
SwingUtilities.invokeLater(r);
}
@Override
public void addCancelledListener(CancelledListener mcl) {
synchronized (listeners) {
listeners.add(mcl);
}
}
@Override
public void removeCancelledListener(CancelledListener mcl) {
synchronized (listeners) {
listeners.remove(mcl);
}
}
@Override
public void addIssueListener(IssueListener listener) {
if (issueListeners == null) {
issueListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
}
}
@Override
public void removeIssueListener(IssueListener listener) {
if (issueListeners != null) {
issueListeners.remove(listener);
}
}
@Override
public void reportIssue(Issue issue) {
if (issueListeners != null) {
for (IssueListener listener : issueListeners) {
listener.issueReported(issue);
}
} }
} }
} }

View file

@ -28,6 +28,7 @@ import org.junit.After;
import org.junit.Test; import org.junit.Test;
import docking.test.AbstractDockingTest; import docking.test.AbstractDockingTest;
import ghidra.util.exception.CancelledException;
public class TaskDialogTest extends AbstractDockingTest { public class TaskDialogTest extends AbstractDockingTest {
@ -95,6 +96,89 @@ public class TaskDialogTest extends AbstractDockingTest {
assertSwingThreadFinishedBeforeTask(); assertSwingThreadFinishedBeforeTask();
} }
/*
* Verifies that if the dialog cancel button is activated, the task is cancelled
*/
@Test
public void testTaskCancel() throws Exception {
SlowModalTask task = new SlowModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
dialogSpy.doShow();
waitForTask();
assertFalse(dialogSpy.isCancelled());
dialogSpy.cancel();
assertTrue(dialogSpy.isCancelled());
}
/*
* Verifies that if the task does not allow cancellation, the cancel button on the GUI
* is disabled
*/
@Test
public void testTaskNoCancel() throws Exception {
SlowModalTask task = new SlowModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
dialogSpy.doShow();
dialogSpy.setCancelEnabled(false);
waitForTask();
assertFalse(dialogSpy.isCancelEnabled());
}
/*
* Verifies that the progress value can be successfully updated
* after using the {@link TaskMonitorService} to retrieve a monitor.
*/
@Test
public void testUpdateProgressSuccess() throws Exception {
TaskLauncher.launch(new Task("task") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
long val = monitor1.getProgress();
monitor1.setProgress(10);
val = monitor1.getProgress();
assertEquals(val, 10);
}
});
}
/*
* Verifies that the progress value will NOT be updated if the caller is a
* secondary monitor. As a bonus, this also verifies that the Task Launcher does
* not lock the task for future progress updates when a new task is launched.
*/
@Test
public void testUpdatePogressFail() throws Exception {
TaskLauncher.launch(new Task("task") {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
TaskMonitor monitor2 = TaskMonitorService.getMonitor();
// Update should be accepted
monitor1.setProgress(10);
// Update should fail
monitor2.setProgress(20);
long val = monitor2.getProgress();
assertEquals(val, 10);
}
});
waitForTasks();
}
private void assertSwingThreadBlockedForTask() { private void assertSwingThreadBlockedForTask() {
TDEvent lastEvent = eventQueue.peekLast(); TDEvent lastEvent = eventQueue.peekLast();
boolean swingIsLast = lastEvent.getThreadName().contains("AWT"); boolean swingIsLast = lastEvent.getThreadName().contains("AWT");

View file

@ -18,9 +18,9 @@ package generic.concurrent;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.*; import ghidra.util.task.CancelledListener;
import ghidra.util.task.TaskMonitor;
/** /**
* This is the FutureTask that will be used to call the {@link QCallback} to work on * This is the FutureTask that will be used to call the {@link QCallback} to work on
@ -131,11 +131,6 @@ class FutureTaskMonitor<I, R> extends FutureTask<R> implements TaskMonitor {
return currentProgress; return currentProgress;
} }
@Override
public void reportIssue(Issue issue) {
// TODO
}
@Override @Override
public boolean cancel(boolean mayInterruptIfRunning) { public boolean cancel(boolean mayInterruptIfRunning) {
boolean result = super.cancel(mayInterruptIfRunning); boolean result = super.cancel(mayInterruptIfRunning);
@ -191,16 +186,6 @@ class FutureTaskMonitor<I, R> extends FutureTask<R> implements TaskMonitor {
} }
} }
@Override
public void addIssueListener(IssueListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void removeIssueListener(IssueListener listener) {
throw new UnsupportedOperationException();
}
private static class ChainedCancelledListener implements CancelledListener { private static class ChainedCancelledListener implements CancelledListener {
private volatile CancelledListener listener1; private volatile CancelledListener listener1;
private volatile CancelledListener listener2; private volatile CancelledListener listener2;

View file

@ -42,6 +42,30 @@ public interface StatusListener {
*/ */
void setStatusText(String text, MessageType type, boolean alert); void setStatusText(String text, MessageType type, boolean alert);
/**
* Sets the subtask text
*
* @param text the text to set
*/
void setSubStatusText(String text);
/**
* Sets the subtask text
*
* @param text the text to set
* @param type the message type
*/
void setSubStatusText(String text, MessageType type);
/**
* Sets the subtask text
*
* @param text the text to set
* @param type the message type
* @param alert if true, an alert will be generated
*/
void setSubStatusText(String text, MessageType type, boolean alert);
/** /**
* Clear the current status - same as setStatusText("") * Clear the current status - same as setStatusText("")
* without being recorded * without being recorded

View file

@ -15,8 +15,6 @@
*/ */
package ghidra.util.task; package ghidra.util.task;
import ghidra.util.Issue;
/** /**
* A monitor that is designed for sub-tasks, where the outer task handles reporting messages and * A monitor that is designed for sub-tasks, where the outer task handles reporting messages and
* progress. This class is really just for checking cancelled. * progress. This class is really just for checking cancelled.
@ -88,11 +86,6 @@ public class CancelOnlyWrappingTaskMonitor extends WrappingTaskMonitor {
return 0; return 0;
} }
@Override
public void reportIssue(Issue issue) {
// ignore
}
@Override @Override
public void setCancelEnabled(boolean enable) { public void setCancelEnabled(boolean enable) {
// ignore // ignore

View file

@ -112,14 +112,19 @@ public abstract class Task implements MonitoredRunnable {
/** /**
* When an object implementing interface <code>Runnable</code> is used to create a thread, * When an object implementing interface <code>Runnable</code> is used to create a thread,
* starting the thread causes the object's <code>run</code> method to be called in that * starting the thread causes the object's <code>run</code> method to be called in that
* separately executing thread * separately executing thread.
* <p>
* Note that the monitor is handed to the {@link TaskMonitorService} here so it will be
* available to any users request it via {@link TaskMonitorService#getMonitor() getMonitor}.
* *
* @param monitor The TaskMonitor * @param monitor the task monitor
*/ */
@Override @Override
public final void monitoredRun(TaskMonitor monitor) { public final void monitoredRun(TaskMonitor monitor) {
this.taskMonitor = monitor; this.taskMonitor = monitor;
int monitorId = TaskMonitorService.register(monitor);
// this will be removed from SystemUtilities in Task.run() after the task is finished // this will be removed from SystemUtilities in Task.run() after the task is finished
TaskUtilities.addTrackedTask(this, monitor); TaskUtilities.addTrackedTask(this, monitor);
@ -136,7 +141,12 @@ public abstract class Task implements MonitoredRunnable {
getTaskTitle() + " - Uncaught Exception: " + t.toString(), t); getTaskTitle() + " - Uncaught Exception: " + t.toString(), t);
} }
finally { finally {
// this is put into SystemUtilities by the TaskLauncher
// This should not be necessary since the thread local object will be cleaned up
// by the GC when the thread terminates, but it's here in case we ever use this
// in conjunction with thread pools and have to manually remove them.
TaskMonitorService.remove(monitorId);
TaskUtilities.removeTrackedTask(this); TaskUtilities.removeTrackedTask(this);
this.taskMonitor = null; this.taskMonitor = null;
} }

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.util.task; package ghidra.util.task;
import ghidra.util.Issue;
import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet; import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -163,19 +162,4 @@ public class TaskMonitorAdapter implements TaskMonitor {
public synchronized void removeCancelledListener(CancelledListener listener) { public synchronized void removeCancelledListener(CancelledListener listener) {
listeners.remove(listener); listeners.remove(listener);
} }
@Override
public void addIssueListener(IssueListener listener) {
// do nothing
}
@Override
public void removeIssueListener(IssueListener listener) {
// do nothing
}
@Override
public void reportIssue(Issue issue) {
// do nothing
}
} }

View file

@ -19,7 +19,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import generic.concurrent.ConcurrentListenerSet; import generic.concurrent.ConcurrentListenerSet;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
public class TaskMonitorSplitter { public class TaskMonitorSplitter {
@ -176,20 +175,5 @@ public class TaskMonitorSplitter {
listener.cancelled(); listener.cancelled();
} }
} }
@Override
public void addIssueListener(IssueListener listener) {
parent.addIssueListener(listener);
}
@Override
public void removeIssueListener(IssueListener listener) {
parent.removeIssueListener(listener);
}
@Override
public void reportIssue(Issue issue) {
parent.reportIssue(issue);
}
} }
} }

View file

@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import ghidra.generic.function.Callback; import ghidra.generic.function.Callback;
import ghidra.util.Issue;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.TimeoutException; import ghidra.util.exception.TimeoutException;
@ -184,11 +183,6 @@ public class TimeoutTaskMonitor implements TaskMonitor {
return delegate.getProgress(); return delegate.getProgress();
} }
@Override
public void reportIssue(Issue issue) {
delegate.reportIssue(issue);
}
private void timeout() { private void timeout() {
didTimeout.set(true); didTimeout.set(true);
timeoutCallback.call(); timeoutCallback.call();
@ -225,14 +219,4 @@ public class TimeoutTaskMonitor implements TaskMonitor {
public void clearCanceled() { public void clearCanceled() {
delegate.clearCanceled(); delegate.clearCanceled();
} }
@Override
public void addIssueListener(IssueListener listener) {
delegate.addIssueListener(listener);
}
@Override
public void removeIssueListener(IssueListener listener) {
delegate.removeIssueListener(listener);
}
} }

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.util.task; package ghidra.util.task;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
/** /**
@ -91,11 +90,6 @@ public class WrappingTaskMonitor implements TaskMonitor {
return delegate.getProgress(); return delegate.getProgress();
} }
@Override
public void reportIssue(Issue issue) {
delegate.reportIssue(issue);
}
@Override @Override
public void cancel() { public void cancel() {
delegate.cancel(); delegate.cancel();
@ -125,14 +119,4 @@ public class WrappingTaskMonitor implements TaskMonitor {
public void clearCanceled() { public void clearCanceled() {
delegate.clearCanceled(); delegate.clearCanceled();
} }
@Override
public void addIssueListener(IssueListener listener) {
delegate.addIssueListener(listener);
}
@Override
public void removeIssueListener(IssueListener listener) {
delegate.removeIssueListener(listener);
}
} }

View file

@ -15,10 +15,7 @@
*/ */
package ghidra.framework.data; package ghidra.framework.data;
import ghidra.util.Issue;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.*; import ghidra.util.task.*;
@ -35,7 +32,6 @@ class LockingTaskMonitor implements TaskMonitor {
private boolean showProgressValue = true; private boolean showProgressValue = true;
private String msg; private String msg;
private MyTaskDialog taskDialog; private MyTaskDialog taskDialog;
private WeakSet<IssueListener> issueListeners;
/** /**
* Constructs a locking task handler for a locked dobj. The setCompleted() method must be * Constructs a locking task handler for a locked dobj. The setCompleted() method must be
@ -263,28 +259,4 @@ class LockingTaskMonitor implements TaskMonitor {
public void removeCancelledListener(CancelledListener listener) { public void removeCancelledListener(CancelledListener listener) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void addIssueListener(IssueListener listener) {
if (issueListeners == null) {
issueListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
}
}
@Override
public void removeIssueListener(IssueListener listener) {
if (issueListeners != null) {
issueListeners.remove(listener);
}
}
@Override
public void reportIssue(Issue issue) {
if (issueListeners != null) {
for (IssueListener listener : issueListeners) {
listener.issueReported(issue);
}
}
}
} }

View file

@ -16,9 +16,9 @@
package ghidra.framework.task; package ghidra.framework.task;
import ghidra.framework.task.gui.GProgressBar; import ghidra.framework.task.gui.GProgressBar;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.*; import ghidra.util.task.CancelledListener;
import ghidra.util.task.TaskMonitor;
/** /**
* Implementation of a TaskMontor that can be "attached" to a GProgressBar. * Implementation of a TaskMontor that can be "attached" to a GProgressBar.
@ -123,11 +123,6 @@ public class GTaskMonitor implements TaskMonitor, CancelledListener {
return progress; return progress;
} }
@Override
public void reportIssue(Issue issue) {
// do nothing for now;
}
@Override @Override
public void cancel() { public void cancel() {
if (cancelEnabled) { if (cancelEnabled) {
@ -145,16 +140,6 @@ public class GTaskMonitor implements TaskMonitor, CancelledListener {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void addIssueListener(IssueListener listener) {
// not currently supported
}
@Override
public void removeIssueListener(IssueListener listener) {
// not currently supported
}
@Override @Override
public void setCancelEnabled(boolean enable) { public void setCancelEnabled(boolean enable) {
cancelEnabled = enable; cancelEnabled = enable;

View file

@ -281,9 +281,11 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
throws IOException, VersionException, LanguageNotFoundException, CancelledException { throws IOException, VersionException, LanguageNotFoundException, CancelledException {
super(dbh, "Untitled", 500, 1000, consumer); super(dbh, "Untitled", 500, 1000, consumer);
if (monitor == null) { if (monitor == null) {
monitor = TaskMonitorAdapter.DUMMY_MONITOR; monitor = TaskMonitorAdapter.DUMMY;
} }
boolean success = false; boolean success = false;
try { try {
int id = startTransaction("create program"); int id = startTransaction("create program");
@ -294,6 +296,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
VersionException dbVersionExc = initializeDatabase(openMode); VersionException dbVersionExc = initializeDatabase(openMode);
VersionException languageVersionExc = null; VersionException languageVersionExc = null;
try { try {
language = DefaultLanguageService.getLanguageService().getLanguage(languageID); language = DefaultLanguageService.getLanguageService().getLanguage(languageID);
languageVersionExc = checkLanguageVersion(openMode); languageVersionExc = checkLanguageVersion(openMode);

View file

@ -28,8 +28,9 @@ public interface LanguageProvider extends ExtensionPoint {
/** /**
* Returns the language with the given name or null if no language has that name * Returns the language with the given name or null if no language has that name
* @param name the name of the language to be retrieved *
* @return the language with the given name * @param languageId the name of the language to be retrieved
* @return the {@link Language} with the given name
*/ */
Language getLanguage(LanguageID languageId); Language getLanguage(LanguageID languageId);
@ -39,7 +40,7 @@ public interface LanguageProvider extends ExtensionPoint {
LanguageDescription[] getLanguageDescriptions(); LanguageDescription[] getLanguageDescriptions();
/** /**
* @return true if one of more laguages or language description failed to load * @return true if one of more languages or language description failed to load
* properly. * properly.
*/ */
boolean hadLoadFailure(); boolean hadLoadFailure();

View file

@ -27,9 +27,10 @@ import java.util.List;
public interface LanguageService { public interface LanguageService {
/** /**
* Returns the language with the given language ID. * Returns the language with the given language ID
* @param languageID the ID of language to retrieve. * @param languageID the ID of language to retrieve
* @throws LanguageNotFoundException if no language can be found for the given ID. * @return the {@link Language} matching the given ID
* @throws LanguageNotFoundException if no language can be found for the given ID
*/ */
Language getLanguage(LanguageID languageID) throws LanguageNotFoundException; Language getLanguage(LanguageID languageID) throws LanguageNotFoundException;
@ -59,13 +60,14 @@ public interface LanguageService {
/** /**
* Returns all known language descriptions which satisfy the criteria identify by the * Returns all known language descriptions which satisfy the criteria identify by the
* non-null parameters. A null value implies a don't-care wildcard value. * non-null parameters. A null value implies a don't-care wildcard value.
* @param processor * @param processor the processor for which to get a language
* @param endianess * @param endianess big or little
* @param size * @param size processor address space size (in bits)
* @param variant * @param variant the processor version (usually 'default')
* @return * @return the language descriptions that fit the parameters
* @deprecated * @deprecated use {@link #getLanguageDescriptions(Processor)} instead
*/ */
@Deprecated
List<LanguageDescription> getLanguageDescriptions(Processor processor, Endian endianess, List<LanguageDescription> getLanguageDescriptions(Processor processor, Endian endianess,
Integer size, String variant); Integer size, String variant);

View file

@ -27,6 +27,8 @@ import generic.jar.ResourceFile;
import ghidra.app.plugin.processors.sleigh.SleighLanguageProvider; import ghidra.app.plugin.processors.sleigh.SleighLanguageProvider;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorService;
/** /**
* Default Language service used gather up all the languages that were found * Default Language service used gather up all the languages that were found
@ -43,6 +45,7 @@ public class DefaultLanguageService implements LanguageService, ChangeListener {
/** /**
* Returns the single instance of the DefaultLanguageService. * Returns the single instance of the DefaultLanguageService.
* @return the language service
*/ */
public static synchronized LanguageService getLanguageService() { public static synchronized LanguageService getLanguageService() {
if (languageService == null) { if (languageService == null) {
@ -94,13 +97,23 @@ public class DefaultLanguageService implements LanguageService, ChangeListener {
*/ */
@Override @Override
public Language getLanguage(LanguageID languageID) throws LanguageNotFoundException { public Language getLanguage(LanguageID languageID) throws LanguageNotFoundException {
TaskMonitor monitor = TaskMonitorService.getMonitor();
monitor.setMessage("Retrieving language: " + languageID);
try {
LanguageInfo info = languageMap.get(languageID); LanguageInfo info = languageMap.get(languageID);
if (info == null) { if (info == null) {
throw new LanguageNotFoundException(languageID); throw new LanguageNotFoundException(languageID);
} }
return info.getLanguage(); Language lang = info.getLanguage();
return lang;
}
finally {
monitor.setMessage("");
}
} }
/** /**
@ -161,8 +174,9 @@ public class DefaultLanguageService implements LanguageService, ChangeListener {
private static boolean languageMatchesExternalProcessor(LanguageDescription description, private static boolean languageMatchesExternalProcessor(LanguageDescription description,
String externalProcessorName, String externalTool) { String externalProcessorName, String externalTool) {
boolean result = false; boolean result = false;
if (externalProcessorName == null) if (externalProcessorName == null) {
result = true; result = true;
}
else if (externalTool != null) { else if (externalTool != null) {
List<String> extNames = description.getExternalNames(externalTool); List<String> extNames = description.getExternalNames(externalTool);
if (extNames != null) { if (extNames != null) {

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.util.task; package ghidra.util.task;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
class StubTaskMonitor implements TaskMonitor { class StubTaskMonitor implements TaskMonitor {
@ -83,12 +82,6 @@ class StubTaskMonitor implements TaskMonitor {
return 0; return 0;
} }
@Override
public void reportIssue(Issue issue) {
// stub
}
@Override @Override
public void cancel() { public void cancel() {
// stub // stub
@ -107,18 +100,6 @@ class StubTaskMonitor implements TaskMonitor {
} }
@Override
public void addIssueListener(IssueListener listener) {
// stub
}
@Override
public void removeIssueListener(IssueListener listener) {
// stub
}
@Override @Override
public void setCancelEnabled(boolean enable) { public void setCancelEnabled(boolean enable) {
// stub // stub

View file

@ -15,49 +15,93 @@
*/ */
package ghidra.util.task; package ghidra.util.task;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
/** /**
* <CODE>TaskMonitor</CODE> provides an interface by means of which a * <CODE>TaskMonitor</CODE> provides an interface by means of which a
* potentially long running task can show its progress and also check if the user * potentially long running task can show its progress and also check if the user
* has cancelled the operation. Operations that support a task monitor should periodically * has cancelled the operation.
* <p>
* Operations that support a task monitor should periodically
* check to see if the operation has been cancelled and abort. If possible, the * check to see if the operation has been cancelled and abort. If possible, the
* operation should also provide periodic progress information. If it can estimate a * operation should also provide periodic progress information. If it can estimate a
* percentage done, then it should use the <code>setProgress(int)</code> method, * percentage done, then it should use the <code>setProgress(int)</code> method,
* otherwise it * otherwise it should just call the <code>setMessage(String)</code> method.
* should just call the <code>setMessage(String)</code> method.
*/ */
public interface TaskMonitor { public interface TaskMonitor {
public static final TaskMonitor DUMMY = new StubTaskMonitor(); public static final TaskMonitor DUMMY = new StubTaskMonitor();
/** /** A value to indicate that this monitor has no progress value set */
* A value to indicate that this monitor has no progress value set.
*/
public static final int NO_PROGRESS_VALUE = -1; public static final int NO_PROGRESS_VALUE = -1;
/** /**
* Returns true if the user has cancelled the operation. * Returns true if the user has cancelled the operation
*
* @return true if the user has cancelled the operation
*/ */
public boolean isCancelled(); public boolean isCancelled();
/** /**
* True (the default) signals to paint the progress information inside of the progress bar. * Returns true if the monitor has been initialized
*
* @return true if the monitor has been initialized
*/
public default boolean isInitialized() {
return false;
}
/**
* Sets the initialization state of the monitor
*
* @param init true for initialized, false otherwise
*/
public default void setInitialized(boolean init) {
// do nothing - this is defaulted for backward compatibility so current
// task monitor implementations do not have to change
}
/**
* Restores the monitor to an uninitialized state. This will result in the primary
* monitor being returned from the {@link TaskMonitorService} on the next
* invocation.
*/
public default void reset() {
synchronized (this) {
setMessage("");
setProgress(0);
setMaximum(0);
setInitialized(false);
clearCanceled();
}
}
/**
* True (the default) signals to paint the progress information inside of the progress bar
* *
* @param showProgressValue true to paint the progress value; false to not * @param showProgressValue true to paint the progress value; false to not
*/ */
public void setShowProgressValue(boolean showProgressValue); public void setShowProgressValue(boolean showProgressValue);
/** /**
* Sets a message giving additional information about the current * Sets the message displayed on the task monitor
* progress. *
* @param message more information * @param message the message to display
*/ */
public void setMessage(String message); public void setMessage(String message);
/** /**
* Sets the current progress value. * Returns a version of this monitor that cannot have its progress state changed. This is
* meant for sub-tasks that should not be allowed to hijack task progress.
*
* @return null
*/
public default TaskMonitor getSecondaryMonitor() {
return null;
}
/**
* Sets the current progress value
* @param value progress value * @param value progress value
*/ */
public void setProgress(long value); public void setProgress(long value);
@ -71,7 +115,7 @@ public interface TaskMonitor {
public void initialize(long max); public void initialize(long max);
/** /**
* Set the progress maximum value. * Set the progress maximum value
* <p><b> * <p><b>
* Note: setting this value will reset the progress to be the max if the progress is currently * Note: setting this value will reset the progress to be the max if the progress is currently
* greater than the new new max value. * greater than the new new max value.
@ -80,88 +124,70 @@ public interface TaskMonitor {
public void setMaximum(long max); public void setMaximum(long max);
/** /**
* Returns the current maximum value for progress. * Returns the current maximum value for progress
* @return * @return the maximum progress value
*/ */
public long getMaximum(); public long getMaximum();
/** /**
* An indeterminate task monitor may choose to show an animation instead of updating progress. * An indeterminate task monitor may choose to show an animation instead of updating progress
* @param indeterminate true if indeterminate
*/ */
public void setIndeterminate(boolean indeterminate); public void setIndeterminate(boolean indeterminate);
/** /**
* Check to see if this monitor has been canceled. * Check to see if this monitor has been canceled
* @throws CancelledException if monitor has been cancelled. * @throws CancelledException if monitor has been cancelled
*/ */
public void checkCanceled() throws CancelledException; public void checkCanceled() throws CancelledException;
/** /**
* A convenience method to increment the current progress by the given value. * A convenience method to increment the current progress by the given value
* @param incrementAmount The amount by which to increment the progress. * @param incrementAmount The amount by which to increment the progress
*/ */
public void incrementProgress(long incrementAmount); public void incrementProgress(long incrementAmount);
/** /**
* Returns the current progress value or {@link #NO_PROGRESS_VALUE} if there is no value * Returns the current progress value or {@link #NO_PROGRESS_VALUE} if there is no value
* set. * set
* @return the current progress value or {@link #NO_PROGRESS_VALUE} if there is no value * @return the current progress value or {@link #NO_PROGRESS_VALUE} if there is no value
* set. * set
*/ */
public long getProgress(); public long getProgress();
/** /**
* Notify that an issue occurred while processing. * Cancel the task
* @param issue the issue that was encountered
*/
public void reportIssue(Issue issue);
/**
* Cancel the task.
*/ */
public void cancel(); public void cancel();
/** /**
* Add cancelled listener. * Add cancelled listener
* @param listener * @param listener the cancel listener
*/ */
public void addCancelledListener(CancelledListener listener); public void addCancelledListener(CancelledListener listener);
/** /**
* Remove cancelled listener. * Remove cancelled listener
* @param listener * @param listener the cancel listener
*/ */
public void removeCancelledListener(CancelledListener listener); public void removeCancelledListener(CancelledListener listener);
/** /**
* Set the enablement of the Cancel button. * Set the enablement of the Cancel button
* @param enable true means to enable the cancel button * @param enable true means to enable the cancel button
*/ */
public void setCancelEnabled(boolean enable); public void setCancelEnabled(boolean enable);
/** /**
* Returns true if cancel ability is enabled * Returns true if cancel ability is enabled
* @return true if cancel ability is enabled
*/ */
public boolean isCancelEnabled(); public boolean isCancelEnabled();
/** /**
* Clear the cancellation so that this TaskMonitor may be reused. * Clear the cancellation so that this TaskMonitor may be reused
* *
*/ */
public void clearCanceled(); public void clearCanceled();
/**
* Add an issue listener to this monitor.
*
* @param listener the listener
*/
public void addIssueListener(IssueListener listener);
/**
* Removes an issue listener to this monitor.
*
* @param listener the listener
*/
public void removeIssueListener(IssueListener listener);
} }

View file

@ -0,0 +1,154 @@
/* ###
* 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.AtomicInteger;
import javax.swing.SwingUtilities;
/**
* Provides access to the {@link TaskMonitor} instance for the current thread. The first
* time a monitor is requested via {@link #getMonitor()}, a "primary" monitor (one
* that allows updating of task progress and status messages) is returned; all
* subsequent requests will return a "secondary" monitor, which only allows
* status message updates. This is to keep the progress bar from being updated
* simultaneously by multiple parties.
* <p>
* Note: {@link TaskMonitor monitor} instances are registered with this service via the
* {@link #register(TaskMonitor) setMonitor} call, and will be available to that thread until
* the {@link #remove(int) remove} method is called.
* <p>
* Note: Because monitor instances are managed by a {@link ThreadLocal} object, they will be
* cleaned up automatically by the GC when the thread is terminated.
*/
public class TaskMonitorService {
/**
* The {@link TaskMonitor} instance. ThreadLocal ensures that each thread has access
* to its own monitor.
*/
private static ThreadLocal<TaskMonitor> localMonitor = new ThreadLocal<TaskMonitor>() {
/**
* Force the initial value to be null so users will have to call
* {@link TaskMonitorService#register(TaskMonitor) register} to assign one
*
* @return null
*/
@Override
protected TaskMonitor initialValue() {
return null;
}
};
/**
* Unique id for each thread monitor that is assigned when a monitor is
* {@link #register(TaskMonitor) registered}. This is to ensure that only clients who have
* a valid id can remove a monitor.
*/
private static ThreadLocal<Integer> localMonitorId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
/**
* Contains the next unique id for the monitor; this is updated each time a new monitor
* is registered with the service.
*/
private static final AtomicInteger nextId = new AtomicInteger(0);
/**
* Returns the task monitor for the current thread. If one has not yet been registered,
* a {@link StubTaskMonitor stub monitor} is returned.
*
* @return the task monitor
*/
public synchronized static TaskMonitor getMonitor() {
if (localMonitor.get() == null) {
// If no monitor is available, just return a stub. The alternative is to throw an
// exception but this isn't considered an error condition in all cases.
localMonitor.set(new StubTaskMonitor());
}
// If the monitor has already been initialized, return the secondary monitor to prevent
// the caller from hijacking the progress bar
if (localMonitor.get().isInitialized()) {
return localMonitor.get().getSecondaryMonitor();
}
// This ensures that the next time this method is called, the service
// will return the secondary monitor
localMonitor.get().setInitialized(true);
return localMonitor.get();
}
/**
* Sets the given monitor for this thread
*
* @param monitor the task monitor to register
* @return the unique id for the monitor
*/
public static int register(TaskMonitor monitor) {
// Don't allow callers to register a monitor if on the swing thread
if (SwingUtilities.isEventDispatchThread()) {
throw new IllegalArgumentException("Attempting to set a monitor in the Swing thread!");
}
// Don't allow users to register a monitor if there is already one registered for this
// thread
if (localMonitor.get() != null) {
throw new IllegalArgumentException("Task monitor already assigned to this thread");
}
localMonitor.set(monitor);
return localMonitorId.get();
}
/**
* Removes the monitor from the thread local object. To protect against clients cavalierly
* removing monitors, a valid monitor id must be provided; this is generated at the time
* of monitor {@link #register(TaskMonitor) registration}.
* <p>
* Note: This should generally not need to be called as the GC will clean up thread local
* objects when the associated thread is finished.
*
* @param monitorId the unique ID for the monitor to be removed
*/
public static void remove(int monitorId) {
if (monitorId != localMonitorId.get()) {
throw new IllegalArgumentException("Invalid monitor id for this thread: " + monitorId);
}
localMonitor.remove();
}
/**
* Hide the constructor - this should not be instantiated
*/
private TaskMonitorService() {
// nothing to do
}
}