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

@ -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
}
}