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());
}
@Override
public void reportIssue(Issue issue) {
dominantMonitor.reportIssue(issue);
slaveMonitor.reportIssue(issue);
}
@Override
public void cancel() {
dominantMonitor.cancel();
@ -1535,16 +1529,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
dominantMonitor.addCancelledListener(listener);
}
@Override
public void addIssueListener(IssueListener listener) {
dominantMonitor.addIssueListener(listener);
}
@Override
public void removeIssueListener(IssueListener listener) {
dominantMonitor.removeIssueListener(listener);
}
@Override
public void setCancelEnabled(boolean enable) {
dominantMonitor.setCancelEnabled(enable);

View file

@ -18,9 +18,9 @@ package ghidra.app.util.headless;
import java.util.Timer;
import java.util.TimerTask;
import ghidra.util.Issue;
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
@ -99,11 +99,6 @@ public class HeadlessTimedTaskMonitor implements TaskMonitor {
return 0;
}
@Override
public void reportIssue(Issue issue) {
// stub
}
@Override
public void cancel() {
timer.cancel(); // Terminate the timer thread
@ -120,16 +115,6 @@ public class HeadlessTimedTaskMonitor implements TaskMonitor {
// stub
}
@Override
public void addIssueListener(IssueListener listener) {
// stub
}
@Override
public void removeIssueListener(IssueListener listener) {
// stub
}
@Override
public void setCancelEnabled(boolean enable) {
// 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;
private boolean isAlerting;
private JLabel statusLabel;
private JLabel subStatusLabel;
private JPanel statusProgPanel; // contains status panel and progress panel
private Timer showTimer;
private TaskScheduler taskScheduler;
@ -608,6 +609,11 @@ public class DialogComponentProvider
public void setStatusText(String text) {
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
@ -621,12 +627,24 @@ public class DialogComponentProvider
setStatusText(message, type, false);
}
@Override
public void setSubStatusText(String message, MessageType type) {
setSubStatusText(message, type, false);
}
@Override
public void setStatusText(String message, MessageType type, boolean alert) {
String text = StringUtils.isBlank(message) ? " " : message;
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) {
@ -641,6 +659,20 @@ public class DialogComponentProvider
alertMessage();
}
}
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
@ -679,11 +711,13 @@ public class DialogComponentProvider
// normal Swing mechanism may not have yet happened).
mainPanel.validate();
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
subStatusLabel.setVisible(false);
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
animator.addTarget(new TimingTargetAdapter() {
@Override
public void end() {
statusLabel.setVisible(true);
subStatusLabel.setVisible(true);
alertFinishedCallback.call();
isAlerting = false;
}
@ -755,12 +789,7 @@ public class DialogComponentProvider
private void showProgressBar(String localTitle, boolean hasProgress, boolean canCancel) {
taskMonitorComponent.setTaskName(localTitle);
taskMonitorComponent.showProgress(hasProgress);
if (canCancel) {
taskMonitorComponent.showCancelButton(true);
}
else {
taskMonitorComponent.showCancelButton(false);
}
taskMonitorComponent.setCancelButtonVisibility(canCancel);
progressCardLayout.show(statusProgPanel, PROGRESS);
rootPanel.validate();
}
@ -799,18 +828,29 @@ public class DialogComponentProvider
public void clearStatusText() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
statusLabel.setText(" ");
subStatusLabel.setText(" ");
updateStatusToolTip();
});
}
/**
* returns the current status in the dialogs status line=
* Returns the current status in the dialogs status line
*
* @return the status text
*/
public String getStatusText() {
return statusLabel.getText();
}
/**
* Returns the secondary status message
*
* @return the secondary status message
*/
public String getSubStatusText() {
return subStatusLabel.getText();
}
protected JLabel getStatusLabel() {
return statusLabel;
}
@ -901,6 +941,14 @@ public class DialogComponentProvider
updateStatusToolTip();
}
});
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
// the message label not visible
@ -908,6 +956,7 @@ public class DialogComponentProvider
panel.add(Box.createVerticalStrut(height), BorderLayout.WEST);
panel.add(statusLabel, BorderLayout.CENTER);
panel.add(subStatusLabel, BorderLayout.SOUTH);
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
*/
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;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.util.AnimatedIcon;
import docking.widgets.OptionDialog;
import docking.widgets.label.GIconLabel;
import ghidra.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.timer.GTimer;
import resources.ResourceManager;
/**
* 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 {
/** Timer used to give the task a chance to complete */
private static final int SLEEPY_TIME = 10;
/** Amount of time to wait before showing the monitor dialog */
private final static int MAX_DELAY = 200000;
public final static int DEFAULT_WIDTH = 275;
private Timer showTimer;
private TaskMonitorComponent monitorComponent;
private AtomicInteger taskID = new AtomicInteger();
private boolean canCancel;
private Runnable updateMessage;
private Runnable closeDialog;
private Runnable enableCancelButton;
private String newMessage;
private boolean cancelState = true;
private Component centerOnComp;
private Runnable shouldCancelRunnable;
private boolean done;
private boolean taskDone;
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,
* otherwise center over parent. If both centerOnComp and parent
* are null, dialog will be centered on screen.
@ -65,7 +88,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
task.hasProgress());
}
/** Creates a new TaskDialog.
/**
* Constructor
*
* @param task the Task that this dialog will be associated with
*/
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 canCancel true if the task can be canceled
* @param isModal true if the dialog should be modal
* @param hasProgress true if the dialog should show a progress bar
*/
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
* parent. If both centerOnComp is null, then the active window will be used
* @param title title for the dialog
@ -99,8 +126,21 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
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) {
this.canCancel = canCancel;
monitorComponent = new TaskMonitorComponent(false, false);
chompingBitsPanel = new ChompingBitsAnimationPanel();
setCancelEnabled(canCancel);
setRememberLocation(false);
setRememberSize(false);
setTransient(true);
@ -108,13 +148,18 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
close();
dispose();
};
updateMessage = () -> {
setStatusText(newMessage);
updatePrimaryMessageRunnable = () -> {
setStatusText(newPrimaryMessage);
synchronized (TaskDialog.this) {
newMessage = null;
newPrimaryMessage = null;
}
};
updateSecondaryMessageRunnable = () -> {
setSubStatusText(newSecondaryMessage);
synchronized (TaskDialog.this) {
newSecondaryMessage = null;
}
};
enableCancelButton = () -> TaskDialog.super.setCancelEnabled(cancelState);
shouldCancelRunnable = () -> {
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());
addWorkPanel(mainPanel);
if (hasProgress) {
installProgressMonitor();
}
@ -142,6 +187,11 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
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() {
boolean userSaysYes = OptionDialog.showYesNoDialog(getComponent(), "Cancel?",
"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() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
mainPanel.removeAll();
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(20, 10, 5, 10));
panel.add(monitorComponent);
mainPanel.add(panel, BorderLayout.NORTH);
mainPanel.add(monitorComponent, BorderLayout.CENTER);
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() {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
mainPanel.removeAll();
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);
mainPanel.add(chompingBitsPanel, BorderLayout.CENTER);
repack();
});
}
@ -200,7 +240,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
monitorComponent.setShowProgressValue(showProgressValue);
}
/** Sets the percentage done.
/**
* Sets the percentage done
*
* @param param The percentage of the task completed.
*/
@Override
@ -250,62 +292,58 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
monitorComponent.setIndeterminate(indeterminate);
}
/** Called if the user presses the cancel button on
* the dialog
*/
@Override
protected void cancelCallback() {
synchronized (this) {
if (!monitorComponent.isCancelEnabled() || monitorComponent.isCancelled()) {
return;
}
}
SwingUtilities.invokeLater(shouldCancelRunnable);
}
/** Sets the message in the TaskDialog dialog
* @param str The message string to be displayed
*/
@Override
synchronized public void setMessage(String str) {
boolean invoke = (newMessage == null);
newMessage = str;
boolean invoke = (newPrimaryMessage == null);
if (invoke) {
SwingUtilities.invokeLater(updateMessage);
newPrimaryMessage = str;
SwingUtilities.invokeLater(updatePrimaryMessageRunnable);
}
}
/**
* Set the enable state of the Cancel button.
* @param enable the state to set the cancel button.
* Updates the secondary message on the task monitor
*
* @param str the string to update
*/
@Override
public void setCancelEnabled(boolean enable) {
if (canCancel) {
monitorComponent.setCancelEnabled(enable);
SwingUtilities.invokeLater(enableCancelButton);
synchronized public void setSecondaryMessage(String str) {
boolean invoke = (newSecondaryMessage == null);
if (invoke) {
newSecondaryMessage = str;
SwingUtilities.invokeLater(updateSecondaryMessageRunnable);
}
}
@Override
public void setCancelEnabled(boolean enable) {
monitorComponent.setCancelEnabled(enable);
super.setCancelEnabled(enable);
}
@Override
public boolean isCancelEnabled() {
return canCancel && cancelState;
return super.isCancelEnabled();
}
public synchronized void taskProcessed() {
done = true;
taskDone = true;
monitorComponent.notifyChangeListeners();
SwingUtilities.invokeLater(closeDialog);
}
@Override
public synchronized void reset() {
done = false;
taskDone = false;
taskID.incrementAndGet();
}
public synchronized boolean isCompleted() {
return done;
return taskDone;
}
@Override
@ -393,7 +431,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
@Override
public synchronized void cancel() {
if (!canCancel || monitorComponent.isCancelled()) {
if (monitorComponent.isCancelled()) {
return;
}
// Mark as cancelled, must be detected by task which should terminate
@ -432,18 +470,11 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
}
@Override
public void addIssueListener(IssueListener listener) {
monitorComponent.addIssueListener(listener);
public synchronized TaskMonitor getSecondaryMonitor() {
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 docking.ToolTipManager;
import docking.util.AnimatedIcon;
import docking.widgets.EmptyBorderButton;
import docking.widgets.OptionDialog;
import docking.widgets.label.GDHtmlLabel;
import docking.widgets.label.GIconLabel;
import ghidra.util.Issue;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.layout.VerticalLayout;
import resources.Icons;
import resources.ResourceManager;
/**
* Component that contains a progress bar, a progress icon, and a cancel
* 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.
*/
public class TaskMonitorComponent extends JPanel implements TaskMonitor {
private WeakSet<CancelledListener> listeners =
WeakDataStructureFactory.createCopyOnReadWeakSet();
private WeakSet<IssueListener> issueListeners;
private JButton cancelButton;
private JPanel eastButtonPanel;
private JProgressBar progressBar;
private JButton cancelButton;
private JPanel cancelPanel;
private JPanel progressBarPanel;
private JPanel mainContentPanel;
private JPanel progressPanel;
private JPanel activeProgressPanel;
private JLabel imageLabel;
private String progressMessage;
private String taskName;
private JLabel messageLabel;
private volatile boolean isCancelled;
private String message;
private long lastProgress = -1;
private long progress;
private long lastMax = -1;
private long max;
private long lastMaxProgress = -1;
private long maxProgress;
private long scaleFactor = 1;
private Runnable updateProgressPanelRunnable;
private Runnable updateCancelButtonRunnable;
private Runnable updateToolTipRunnable;
private JLabel messageLabel;
private Runnable shouldCancelRunnable;
private boolean showingProgress = true;
private boolean showingIcon = true;
private boolean showingCancelButton = true;
private boolean cancelEnabled = true;
private boolean paintProgressValue = true;
private AtomicBoolean isIndeterminate = new AtomicBoolean(false);
private AtomicInteger taskID = new AtomicInteger();
private Timer updateTimer;
private Runnable shouldCancelRunnable;
private boolean paintProgressValue = true;
private NumberFormat percentFormat = NumberFormat.getPercentInstance();
private long scaleFactor = 1;
/**
* Construct a new TaskMonitorComponent.
* @param l listener that is notified when the task completes or the
* user cancels the task
* Constructor
*/
public TaskMonitorComponent() {
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) {
updateProgressPanelRunnable = () -> updateProgressPanel();
updateCancelButtonRunnable = () -> updateCancelButton();
@ -113,13 +117,25 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
buildProgressPanel(includeTextField, includeCancelButton);
}
@Override
public void addCancelledListener(CancelledListener mcl) {
listeners.add(mcl);
}
/**
* Reset this monitor so that it can be reused.
*/
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
@ -136,7 +152,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
@Override
public synchronized void setMessage(String message) {
this.message = message;
this.progressMessage = message;
startUpdateTimer();
}
@ -148,13 +164,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
progress = value;
startUpdateTimer();
}
private synchronized void startUpdateTimer() {
if (!updateTimer.isRunning()) {
updateTimer.start();
}
}
@Override
public void initialize(long maxValue) {
setMaximum(maxValue);
@ -163,13 +173,13 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
@Override
public void setMaximum(long max) {
this.max = max;
if (progress > this.max) {
this.maxProgress = max;
if (progress > this.maxProgress) {
progress = max;
}
startUpdateTimer();
}
/**
* Sets the <code>indeterminate</code> property of the progress bar,
* which determines whether the progress bar is in determinate
@ -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
public synchronized void setCancelEnabled(boolean enable) {
if (cancelEnabled != enable) {
@ -243,67 +242,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
isCancelled = false;
}
}
/**
* 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
public void setShowProgressValue(boolean showProgressValue) {
this.paintProgressValue = showProgressValue;
@ -312,19 +251,121 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
@Override
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() {
if (message != null) {
messageLabel.setText(message);
message = null;
if (progressMessage != null) {
messageLabel.setText(progressMessage);
progressMessage = null;
}
if (max != lastMax) {
setMaxValueInProgressBar(max);
lastMax = max;
if (maxProgress != lastMaxProgress) {
setMaxValueInProgressBar(maxProgress);
lastMaxProgress = maxProgress;
}
if (progress != lastProgress) {
@ -379,10 +420,10 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
private synchronized void updateProgressPanel() {
if (showingProgress) {
progressPanel.add(progressBar, BorderLayout.NORTH);
progressBarPanel.add(progressBar, BorderLayout.NORTH);
}
else {
progressPanel.remove(progressBar);
progressBarPanel.remove(progressBar);
}
}
@ -424,24 +465,24 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
progressBar.setStringPainted(true);
ToolTipManager.sharedInstance().registerComponent(progressBar);
createAnimatedIcon();
progressPanel = new HourglassAnimationPanel();
progressPanel = new JPanel(new VerticalLayout(0));
progressPanel.add(progressBar);
progressBarPanel = new JPanel(new VerticalLayout(0));
progressBarPanel.add(progressBar);
if (includeTextField) {
progressPanel.add(messageLabel);
progressBarPanel.add(messageLabel);
progressBar.setPreferredSize(new Dimension(180, 12));
}
else {
progressBar.setBorderPainted(true);
Dimension size = progressBar.getPreferredSize();
progressPanel.setBorder(BorderFactory.createEmptyBorder(
(imageLabel.getPreferredSize().height - size.height) / 2, 0, 0, 8));
progressBarPanel.setBorder(BorderFactory.createEmptyBorder(
(progressPanel.getPreferredSize().height - size.height) / 2, 0, 0, 8));
}
activeProgressPanel = new JPanel(new BorderLayout());
activeProgressPanel.add(progressPanel, BorderLayout.CENTER);
activeProgressPanel.add(imageLabel, BorderLayout.EAST);
mainContentPanel = new JPanel(new BorderLayout());
mainContentPanel.add(progressBarPanel, BorderLayout.CENTER);
mainContentPanel.add(progressPanel, BorderLayout.EAST);
ImageIcon icon = Icons.STOP_ICON;
cancelButton = new EmptyBorderButton(icon);
@ -452,86 +493,15 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
cancelButton.setFocusable(false);
cancelButton.setRolloverEnabled(true);
add(activeProgressPanel, BorderLayout.CENTER);
add(mainContentPanel, BorderLayout.CENTER);
if (includeCancelButton) {
eastButtonPanel = new JPanel();
eastButtonPanel.setLayout(new BoxLayout(eastButtonPanel, BoxLayout.Y_AXIS));
eastButtonPanel.add(Box.createVerticalGlue());
eastButtonPanel.add(cancelButton);
eastButtonPanel.add(Box.createVerticalGlue());
add(eastButtonPanel, 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);
}
cancelPanel = new JPanel();
cancelPanel.setLayout(new BoxLayout(cancelPanel, BoxLayout.Y_AXIS));
cancelPanel.add(Box.createVerticalGlue());
cancelPanel.add(cancelButton);
cancelPanel.add(Box.createVerticalGlue());
add(cancelPanel, BorderLayout.EAST);
}
}
}

View file

@ -28,6 +28,7 @@ import org.junit.After;
import org.junit.Test;
import docking.test.AbstractDockingTest;
import ghidra.util.exception.CancelledException;
public class TaskDialogTest extends AbstractDockingTest {
@ -94,6 +95,89 @@ public class TaskDialogTest extends AbstractDockingTest {
assertTrue(dialogSpy.wasShown());
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() {
TDEvent lastEvent = eventQueue.peekLast();
@ -160,7 +244,7 @@ public class TaskDialogTest extends AbstractDockingTest {
public TaskDialogSpy(Task task) {
super(task);
}
@Override
protected void doShow() {
shown.set(true);

View file

@ -18,9 +18,9 @@ package generic.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import ghidra.util.Issue;
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
@ -131,11 +131,6 @@ class FutureTaskMonitor<I, R> extends FutureTask<R> implements TaskMonitor {
return currentProgress;
}
@Override
public void reportIssue(Issue issue) {
// TODO
}
@Override
public boolean cancel(boolean 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 volatile CancelledListener listener1;
private volatile CancelledListener listener2;

View file

@ -41,6 +41,30 @@ public interface StatusListener {
* @param alert true to grab the user's attention
*/
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("")

View file

@ -15,8 +15,6 @@
*/
package ghidra.util.task;
import ghidra.util.Issue;
/**
* 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.
@ -88,11 +86,6 @@ public class CancelOnlyWrappingTaskMonitor extends WrappingTaskMonitor {
return 0;
}
@Override
public void reportIssue(Issue issue) {
// ignore
}
@Override
public void setCancelEnabled(boolean enable) {
// 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,
* 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
public final void monitoredRun(TaskMonitor monitor) {
this.taskMonitor = monitor;
int monitorId = TaskMonitorService.register(monitor);
// this will be removed from SystemUtilities in Task.run() after the task is finished
TaskUtilities.addTrackedTask(this, monitor);
@ -136,9 +141,14 @@ public abstract class Task implements MonitoredRunnable {
getTaskTitle() + " - Uncaught Exception: " + t.toString(), t);
}
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);
this.taskMonitor = null;
this.taskMonitor = null;
}
notifyTaskListeners(isCancelled);

View file

@ -15,7 +15,6 @@
*/
package ghidra.util.task;
import ghidra.util.Issue;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
@ -163,19 +162,4 @@ public class TaskMonitorAdapter implements TaskMonitor {
public synchronized void removeCancelledListener(CancelledListener 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 generic.concurrent.ConcurrentListenerSet;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException;
public class TaskMonitorSplitter {
@ -176,20 +175,5 @@ public class TaskMonitorSplitter {
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 ghidra.generic.function.Callback;
import ghidra.util.Issue;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.TimeoutException;
@ -184,11 +183,6 @@ public class TimeoutTaskMonitor implements TaskMonitor {
return delegate.getProgress();
}
@Override
public void reportIssue(Issue issue) {
delegate.reportIssue(issue);
}
private void timeout() {
didTimeout.set(true);
timeoutCallback.call();
@ -225,14 +219,4 @@ public class TimeoutTaskMonitor implements TaskMonitor {
public void 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;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException;
/**
@ -91,11 +90,6 @@ public class WrappingTaskMonitor implements TaskMonitor {
return delegate.getProgress();
}
@Override
public void reportIssue(Issue issue) {
delegate.reportIssue(issue);
}
@Override
public void cancel() {
delegate.cancel();
@ -125,14 +119,4 @@ public class WrappingTaskMonitor implements TaskMonitor {
public void 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;
import ghidra.util.Issue;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
@ -35,7 +32,6 @@ class LockingTaskMonitor implements TaskMonitor {
private boolean showProgressValue = true;
private String msg;
private MyTaskDialog taskDialog;
private WeakSet<IssueListener> issueListeners;
/**
* 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) {
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;
import ghidra.framework.task.gui.GProgressBar;
import ghidra.util.Issue;
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.
@ -123,11 +123,6 @@ public class GTaskMonitor implements TaskMonitor, CancelledListener {
return progress;
}
@Override
public void reportIssue(Issue issue) {
// do nothing for now;
}
@Override
public void cancel() {
if (cancelEnabled) {
@ -145,16 +140,6 @@ public class GTaskMonitor implements TaskMonitor, CancelledListener {
throw new UnsupportedOperationException();
}
@Override
public void addIssueListener(IssueListener listener) {
// not currently supported
}
@Override
public void removeIssueListener(IssueListener listener) {
// not currently supported
}
@Override
public void setCancelEnabled(boolean enable) {
cancelEnabled = enable;

View file

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

View file

@ -28,18 +28,19 @@ public interface LanguageProvider extends ExtensionPoint {
/**
* 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);
/**
* Returns a list of language descriptions provided by this provider
*/
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.
*/
boolean hadLoadFailure();

View file

@ -27,12 +27,13 @@ import java.util.List;
public interface LanguageService {
/**
* Returns the language with the given language ID.
* @param languageID the ID of language to retrieve.
* @throws LanguageNotFoundException if no language can be found for the given ID.
* Returns the language with the given language ID
* @param languageID the ID of language to retrieve
* @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;
/**
* Returns the default Language to use for the given processor;
* @param processor the processor for which to get a language.
@ -59,13 +60,14 @@ public interface LanguageService {
/**
* 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.
* @param processor
* @param endianess
* @param size
* @param variant
* @return
* @deprecated
* @param processor the processor for which to get a language
* @param endianess big or little
* @param size processor address space size (in bits)
* @param variant the processor version (usually 'default')
* @return the language descriptions that fit the parameters
* @deprecated use {@link #getLanguageDescriptions(Processor)} instead
*/
@Deprecated
List<LanguageDescription> getLanguageDescriptions(Processor processor, Endian endianess,
Integer size, String variant);

View file

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

View file

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

View file

@ -15,49 +15,93 @@
*/
package ghidra.util.task;
import ghidra.util.Issue;
import ghidra.util.exception.CancelledException;
/**
* <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
* 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
* operation should also provide periodic progress information. If it can estimate a
* percentage done, then it should use the <code>setProgress(int)</code> method,
* otherwise it
* should just call the <code>setMessage(String)</code> method.
* otherwise it should just call the <code>setMessage(String)</code> method.
*/
public interface TaskMonitor {
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;
/**
* 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();
/**
* 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
*/
public void setShowProgressValue(boolean showProgressValue);
/**
* Sets a message giving additional information about the current
* progress.
* @param message more information
* Sets the message displayed on the task monitor
*
* @param message the message to display
*/
public void setMessage(String message);
/**
* 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.
* Sets the current progress value
* @param value progress value
*/
public void setProgress(long value);
@ -71,7 +115,7 @@ public interface TaskMonitor {
public void initialize(long max);
/**
* Set the progress maximum value.
* Set the progress maximum value
* <p><b>
* Note: setting this value will reset the progress to be the max if the progress is currently
* greater than the new new max value.
@ -80,88 +124,70 @@ public interface TaskMonitor {
public void setMaximum(long max);
/**
* Returns the current maximum value for progress.
* @return
* Returns the current maximum value for progress
* @return the maximum progress value
*/
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);
/**
* Check to see if this monitor has been canceled.
* @throws CancelledException if monitor has been cancelled.
* Check to see if this monitor has been canceled
* @throws CancelledException if monitor has been cancelled
*/
public void checkCanceled() throws CancelledException;
/**
* A convenience method to increment the current progress by the given value.
* @param incrementAmount The amount by which to increment the progress.
* A convenience method to increment the current progress by the given value
* @param incrementAmount The amount by which to increment the progress
*/
public void incrementProgress(long incrementAmount);
/**
* 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
* set.
* set
*/
public long getProgress();
/**
* Notify that an issue occurred while processing.
* @param issue the issue that was encountered
*/
public void reportIssue(Issue issue);
/**
* Cancel the task.
* Cancel the task
*/
public void cancel();
/**
* Add cancelled listener.
* @param listener
* Add cancelled listener
* @param listener the cancel listener
*/
public void addCancelledListener(CancelledListener listener);
/**
* Remove cancelled listener.
* @param listener
* Remove cancelled listener
* @param listener the cancel 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
*/
public void setCancelEnabled(boolean enable);
/**
* Returns true if cancel ability is enabled
* @return true if cancel ability is enabled
*/
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();
/**
* 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
}
}