mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-25 19:05:32 +08:00
Merge branch 'GP-463_dragonmacher_PR-2534_sudofox_exclusive-checkout-typo-fix'
This commit is contained in:
@@ -49,6 +49,7 @@ import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.task.AbstractSwingUpdateManager;
|
||||
import ghidra.util.task.SwingUpdateManager;
|
||||
import junit.framework.AssertionFailedError;
|
||||
import sun.awt.AppContext;
|
||||
@@ -450,6 +451,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
||||
// a more reasonable number
|
||||
//
|
||||
// Update: 744 references at 12/1/19
|
||||
// Update: 559 references at 12/4/20
|
||||
public static void waitForPostedSwingRunnables() {
|
||||
waitForSwing();
|
||||
}
|
||||
@@ -1594,13 +1596,13 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
||||
throw new AssertException("Can't wait for swing from within the swing thread!");
|
||||
}
|
||||
|
||||
Set<SwingUpdateManager> set = new HashSet<>();
|
||||
Set<AbstractSwingUpdateManager> set = new HashSet<>();
|
||||
runSwing(() -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
WeakSet<SwingUpdateManager> s =
|
||||
(WeakSet<SwingUpdateManager>) getInstanceField("instances",
|
||||
SwingUpdateManager.class);
|
||||
for (SwingUpdateManager manager : s) {
|
||||
for (AbstractSwingUpdateManager manager : s) {
|
||||
set.add(manager);
|
||||
}
|
||||
});
|
||||
@@ -1617,7 +1619,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
||||
return wasEverBusy;
|
||||
}
|
||||
|
||||
private static boolean waitForSwing(Set<SwingUpdateManager> managers, boolean flush) {
|
||||
private static boolean waitForSwing(Set<AbstractSwingUpdateManager> managers, boolean flush) {
|
||||
|
||||
// Note: not sure how long is too long to wait for the Swing thread and update managers
|
||||
// to finish. This is usually less than a second. We have seen a degenerate
|
||||
@@ -1637,7 +1639,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
||||
yieldToSwing();
|
||||
keepGoing = false;
|
||||
|
||||
for (SwingUpdateManager manager : managers) {
|
||||
for (AbstractSwingUpdateManager manager : managers) {
|
||||
|
||||
if (!manager.isBusy()) {
|
||||
// no current or pending work
|
||||
@@ -1674,7 +1676,7 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
||||
return wasEverBusy;
|
||||
}
|
||||
|
||||
private static void flushAllManagers(Set<SwingUpdateManager> managers, boolean flush) {
|
||||
private static void flushAllManagers(Set<AbstractSwingUpdateManager> managers, boolean flush) {
|
||||
|
||||
//
|
||||
// Some update managers will make an update that causes another manager to schedule an
|
||||
@@ -1691,21 +1693,19 @@ public abstract class AbstractGenericTest extends AbstractGTest {
|
||||
// which would be 2
|
||||
int n = 3;
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (SwingUpdateManager manager : managers) {
|
||||
for (AbstractSwingUpdateManager manager : managers) {
|
||||
doFlush(flush, manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void doFlush(boolean doFlush, SwingUpdateManager manager) {
|
||||
private static void doFlush(boolean doFlush, AbstractSwingUpdateManager manager) {
|
||||
if (!doFlush) {
|
||||
return;
|
||||
}
|
||||
|
||||
runSwing(() -> {
|
||||
if (manager.hasPendingUpdates()) {
|
||||
manager.updateNow();
|
||||
}
|
||||
manager.flush();
|
||||
}, false);
|
||||
yieldToSwing();
|
||||
}
|
||||
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
/* ###
|
||||
* 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 javax.swing.Timer;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
/**
|
||||
* A base class to allow clients to buffer events. UI components may receive numbers events to make
|
||||
* changes to their underlying data model. Further, for many of these clients, it is sufficient
|
||||
* to perform one update to capture all of the changes. In this scenario, the client can use this
|
||||
* class to keep pushing off internal updates until: 1) the flurry of events has settled down, or
|
||||
* 2) some specified amount of time has expired.
|
||||
* <p>
|
||||
* The various methods dictate when the client will get a callback:<p>
|
||||
* <ul>
|
||||
* <li>{@link #update()} - if this is the first call to <code>update</code>, then do the work
|
||||
* immediately; otherwise, buffer the update request until the
|
||||
* timeout has expired.</li>
|
||||
* <li>{@link #updateNow()} - perform the callback now.</li>
|
||||
* <li>{@link #updateLater()} - buffer the update request until the timeout has expired.</li>
|
||||
* <li>Non-blocking update now - this is a conceptual use-case, where the client wishes to perform an
|
||||
* immediate update, but not during the current Swing event. To achieve
|
||||
* this, you could call something like:
|
||||
* <pre>{@literal
|
||||
* SwingUtilities.invokeLater(() -> updateManager.updateNow());
|
||||
* }</pre>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <P> This class is safe to use in a multi-threaded environment. State variables are guarded
|
||||
* via synchronization on this object. The Swing thread is used to perform updates, which
|
||||
* guarantees that only one update will happen at a time.
|
||||
*/
|
||||
public abstract class AbstractSwingUpdateManager {
|
||||
|
||||
protected static final long NONE = 0;
|
||||
public static final int DEFAULT_MAX_DELAY = 30000;
|
||||
protected static final int MIN_DELAY_FLOOR = 10;
|
||||
protected static final int DEFAULT_MIN_DELAY = 250;
|
||||
protected static final String DEFAULT_NAME = AbstractSwingUpdateManager.class.getSimpleName();
|
||||
private static final WeakSet<AbstractSwingUpdateManager> instances =
|
||||
WeakDataStructureFactory.createCopyOnReadWeakSet();
|
||||
|
||||
protected final Timer timer;
|
||||
private final int minDelay;
|
||||
private final int maxDelay;
|
||||
|
||||
private final String name;
|
||||
private String inceptionInformation;
|
||||
|
||||
protected long requestTime = NONE;
|
||||
protected long bufferingStartTime;
|
||||
protected boolean disposed = false;
|
||||
|
||||
// This is true when work has begun and is not finished. This is only mutated on the
|
||||
// Swing thread, but is read by other threads.
|
||||
protected boolean isWorking;
|
||||
|
||||
/**
|
||||
* Constructs a new SwingUpdateManager with default values for min and max delay. See
|
||||
* {@link #DEFAULT_MIN_DELAY} and {@value #DEFAULT_MAX_DELAY}.
|
||||
*/
|
||||
protected AbstractSwingUpdateManager() {
|
||||
this(DEFAULT_MIN_DELAY, DEFAULT_MAX_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new AbstractSwingUpdateManager
|
||||
* <p>
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR},
|
||||
* regardless of the given value.
|
||||
*
|
||||
* @param minDelay the minimum number of milliseconds to wait once the event stream stops
|
||||
* coming in before actually updating the screen
|
||||
*/
|
||||
protected AbstractSwingUpdateManager(int minDelay) {
|
||||
this(minDelay, DEFAULT_MAX_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new AbstractSwingUpdateManager
|
||||
* <p>
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR},
|
||||
* regardless of the given value.
|
||||
*
|
||||
* @param minDelay the minimum number of milliseconds to wait once the event stream stops
|
||||
* coming in before actually updating the screen.
|
||||
* @param maxDelay the maximum amount of time to wait between gui updates.
|
||||
*/
|
||||
protected AbstractSwingUpdateManager(int minDelay, int maxDelay) {
|
||||
this(minDelay, maxDelay, DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new AbstractSwingUpdateManager
|
||||
* <p>
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR},
|
||||
* regardless of the given value.
|
||||
*
|
||||
* @param minDelay the minimum number of milliseconds to wait once the event stream stops
|
||||
* coming in before actually updating the screen.
|
||||
* @param maxDelay the maximum amount of time to wait between gui updates.
|
||||
* @param name The name of this update manager; this allows for selective trace logging
|
||||
*/
|
||||
protected AbstractSwingUpdateManager(int minDelay, int maxDelay, String name) {
|
||||
|
||||
this.maxDelay = maxDelay;
|
||||
this.name = name;
|
||||
|
||||
recordInception();
|
||||
this.minDelay = Math.max(MIN_DELAY_FLOOR, minDelay);
|
||||
timer = new Timer(minDelay, e -> timerCallback());
|
||||
timer.setRepeats(false);
|
||||
instances.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The subclass callback to perform work.
|
||||
*/
|
||||
protected abstract void swingDoWork();
|
||||
|
||||
/**
|
||||
* Signals to perform an update. See the class header for the usage of the various
|
||||
* update methods.
|
||||
*/
|
||||
protected synchronized void update() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestTime = System.currentTimeMillis();
|
||||
SystemUtilities.runSwingLater(this::checkForWork);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to perform an update. See the class header for the usage of the various
|
||||
* update methods.
|
||||
*/
|
||||
protected synchronized void updateLater() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestTime = System.currentTimeMillis();
|
||||
bufferingStartTime = bufferingStartTime == NONE ? requestTime : bufferingStartTime;
|
||||
scheduleCheckForWork();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to perform an update. See the class header for the usage of the various
|
||||
* update methods.
|
||||
*/
|
||||
protected void updateNow() {
|
||||
synchronized (this) {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestTime = System.currentTimeMillis();
|
||||
bufferingStartTime = NONE; // set so that the max delay check will trigger work
|
||||
}
|
||||
SystemUtilities.runSwingNow(this::checkForWork);
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes this run manager to run if it has a pending update
|
||||
*/
|
||||
public void flush() {
|
||||
if (hasPendingUpdates()) {
|
||||
SystemUtilities.runSwingNow(this::checkForWork);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to stop any buffered work. This will not stop any in-progress work.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
timer.stop();
|
||||
requestTime = NONE;
|
||||
bufferingStartTime = NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a pending request that hasn't started yet. Any currently
|
||||
* executing requests will not affect this call.
|
||||
*
|
||||
* @return true if there is a pending request that hasn't started yet.
|
||||
*/
|
||||
public synchronized boolean hasPendingUpdates() {
|
||||
if (disposed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return requestTime != NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any work is being performed or if there is buffered work
|
||||
* @return true if any work is being performed or if there is buffered work
|
||||
*/
|
||||
public synchronized boolean isBusy() {
|
||||
if (disposed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return requestTime != NONE || isWorking;
|
||||
}
|
||||
|
||||
public synchronized void dispose() {
|
||||
timer.stop();
|
||||
instances.remove(this);
|
||||
requestTime = NONE;
|
||||
bufferingStartTime = NONE;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
public synchronized boolean isDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " @ " + inceptionInformation;
|
||||
}
|
||||
|
||||
public String toStringDebug() {
|
||||
//@formatter:off
|
||||
return "{\n" +
|
||||
"\tname: " + name + "\n" +
|
||||
"\tcreator: " + inceptionInformation + " ("+System.identityHashCode(this)+")\n" +
|
||||
"\trequest time: "+requestTime + "\n" +
|
||||
"\twork count: " + isWorking + "\n" +
|
||||
"}";
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
// note: this is called on the Swing thread
|
||||
protected void checkForWork() {
|
||||
|
||||
if (shouldDoWork()) {
|
||||
swingExecutePendingWork();
|
||||
}
|
||||
}
|
||||
|
||||
// This is similar to checkForWork except that it resets the task buffering when
|
||||
// the time expires and there is no work to do.
|
||||
private void timerCallback() {
|
||||
|
||||
if (shouldDoWork()) {
|
||||
swingExecutePendingWork();
|
||||
}
|
||||
else if (requestTime == NONE) {
|
||||
bufferingStartTime = NONE; // The timer has fired and there is no pending work
|
||||
}
|
||||
}
|
||||
|
||||
// note: this is called on the Swing thread
|
||||
private synchronized boolean shouldDoWork() {
|
||||
|
||||
// If no pending request, exit without restarting timer
|
||||
if (requestTime == NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (isTimeToWork(now)) {
|
||||
bufferingStartTime = now;
|
||||
requestTime = NONE;
|
||||
isWorking = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
scheduleCheckForWork();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void scheduleCheckForWork() {
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private boolean isTimeToWork(long now) {
|
||||
|
||||
// if past maximum delay, always do work
|
||||
long timeSinceBufferingStart = now - bufferingStartTime;
|
||||
if (timeSinceBufferingStart > maxDelay) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if no new requests have come in since the last time we checked, do work
|
||||
long timeSinceLastRequest = now - requestTime;
|
||||
if (timeSinceLastRequest > minDelay) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// note: this is called on the Swing thread
|
||||
private void swingExecutePendingWork() {
|
||||
try {
|
||||
swingDoWork();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// catch exceptions so we don't kill the timer
|
||||
Msg.showError(this, null, "Unexpected Exception",
|
||||
"Unexpected exception in Swing Update Manager", t);
|
||||
}
|
||||
|
||||
isWorking = false;
|
||||
|
||||
// we need to clear the buffering flag after the minDelay has passed, so start the timer
|
||||
scheduleCheckForWork();
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inception Info
|
||||
//==================================================================================================
|
||||
|
||||
private void recordInception() {
|
||||
inceptionInformation = getInceptionFromTheFirstClassThatIsNotUs();
|
||||
}
|
||||
|
||||
private String getInceptionFromTheFirstClassThatIsNotUs() {
|
||||
Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(getClass());
|
||||
|
||||
StackTraceElement[] trace = t.getStackTrace();
|
||||
String classInfo = trace[0].toString();
|
||||
|
||||
/*
|
||||
// debug source of creation
|
||||
Throwable filtered = ReflectionUtilities.filterJavaThrowable(t);
|
||||
String string = ReflectionUtilities.stackTraceToString(filtered);
|
||||
classInfo = classInfo + "\n\tfrom:\n\n" + string;
|
||||
*/
|
||||
|
||||
return classInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/* ###
|
||||
* 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 utility.function.Dummy;
|
||||
|
||||
/**
|
||||
* A class that run the client's runnable on the Swing thread. Repeated requests will get buffered
|
||||
* until the max delay is reached.
|
||||
*/
|
||||
public class BufferedSwingRunner extends AbstractSwingUpdateManager {
|
||||
|
||||
private Runnable nextRunnable;
|
||||
|
||||
/**
|
||||
* Constructs a new SwingUpdateManager
|
||||
* <p>
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR},
|
||||
* regardless of the given value.
|
||||
*
|
||||
* @param minDelay the minimum number of milliseconds to wait once the event stream stops
|
||||
* coming in before actually updating the screen.
|
||||
* @param maxDelay the maximum amount of time to wait between gui updates.
|
||||
*/
|
||||
public BufferedSwingRunner(int minDelay, int maxDelay) {
|
||||
super(minDelay, maxDelay, DEFAULT_NAME);
|
||||
}
|
||||
|
||||
public BufferedSwingRunner() {
|
||||
super(DEFAULT_MIN_DELAY, DEFAULT_MAX_DELAY, DEFAULT_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void swingDoWork() {
|
||||
Runnable currentRunnable = prepareCurrentRunnable();
|
||||
currentRunnable.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given runnable. If this is the first call to <code>run</code>, then do the work
|
||||
* immediately; otherwise, buffer the request until the timeout has expired.
|
||||
*
|
||||
* <p>See the header of {@link AbstractSwingUpdateManager} for details on the update process.
|
||||
*
|
||||
* @param r the task to run on the Swing thread
|
||||
*/
|
||||
public synchronized void run(Runnable r) {
|
||||
this.nextRunnable = r;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given runnable later, buffering the request until the timeout has expired.
|
||||
*
|
||||
* <p>See the header of {@link AbstractSwingUpdateManager} for details on the update process.
|
||||
*
|
||||
* @param r the task to run on the Swing thread
|
||||
*/
|
||||
public synchronized void runLater(Runnable r) {
|
||||
this.nextRunnable = r;
|
||||
updateLater();
|
||||
}
|
||||
|
||||
private synchronized Runnable prepareCurrentRunnable() {
|
||||
Runnable currentRunnable = nextRunnable;
|
||||
nextRunnable = null;
|
||||
return Dummy.ifNull(currentRunnable);
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,7 @@
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
import javax.swing.Timer;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
/**
|
||||
* A class to allow clients to buffer events. UI components may receive numbers events to make
|
||||
@@ -50,31 +44,10 @@ import utilities.util.reflection.ReflectionUtilities;
|
||||
* via synchronization on this object. The Swing thread is used to perform updates, which
|
||||
* guarantees that only one update will happen at a time.
|
||||
*/
|
||||
public class SwingUpdateManager {
|
||||
private static final long NONE = 0;
|
||||
public static final int DEFAULT_MAX_DELAY = 30000;
|
||||
private static final int MIN_DELAY_FLOOR = 10;
|
||||
private static final int DEFAULT_MIN_DELAY = 250;
|
||||
private static final String DEFAULT_NAME = SwingUpdateManager.class.getSimpleName();
|
||||
private static final WeakSet<SwingUpdateManager> instances =
|
||||
WeakDataStructureFactory.createCopyOnReadWeakSet();
|
||||
public class SwingUpdateManager extends AbstractSwingUpdateManager {
|
||||
|
||||
private final Timer timer;
|
||||
private final int minDelay;
|
||||
private final int maxDelay;
|
||||
private final Runnable clientRunnable;
|
||||
|
||||
private final String name;
|
||||
private String inceptionInformation;
|
||||
|
||||
private long requestTime = NONE;
|
||||
private long bufferingStartTime;
|
||||
private boolean disposed = false;
|
||||
|
||||
// This is true when work has begun and is not finished. This is only mutated on the
|
||||
// Swing thread, but is read by other threads.
|
||||
private boolean isWorking;
|
||||
|
||||
/**
|
||||
* Constructs a new SwingUpdateManager with default values for min and max delay. See
|
||||
* {@link #DEFAULT_MIN_DELAY} and {@value #DEFAULT_MAX_DELAY}.
|
||||
@@ -88,8 +61,8 @@ public class SwingUpdateManager {
|
||||
/**
|
||||
* Constructs a new SwingUpdateManager
|
||||
* <p>
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR}, regardless of
|
||||
* the given value.
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR},
|
||||
* regardless of the given value.
|
||||
*
|
||||
* @param minDelay the minimum number of milliseconds to wait once the event stream stops
|
||||
* coming in before actually updating the screen.
|
||||
@@ -102,8 +75,8 @@ public class SwingUpdateManager {
|
||||
/**
|
||||
* Constructs a new SwingUpdateManager
|
||||
* <p>
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR}, regardless of
|
||||
* the given value.
|
||||
* <b>Note: </b>The <code>minDelay</code> will always be at least {@link #MIN_DELAY_FLOOR},
|
||||
* regardless of the given value.
|
||||
*
|
||||
* @param minDelay the minimum number of milliseconds to wait once the event stream stops
|
||||
* coming in before actually updating the screen.
|
||||
@@ -111,7 +84,8 @@ public class SwingUpdateManager {
|
||||
* @param r the runnable that performs the client work.
|
||||
*/
|
||||
public SwingUpdateManager(int minDelay, int maxDelay, Runnable r) {
|
||||
this(minDelay, maxDelay, DEFAULT_NAME, r);
|
||||
super(minDelay, maxDelay, DEFAULT_NAME);
|
||||
this.clientRunnable = r;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,39 +101,34 @@ public class SwingUpdateManager {
|
||||
* @param r the runnable that performs the client work.
|
||||
*/
|
||||
public SwingUpdateManager(int minDelay, int maxDelay, String name, Runnable r) {
|
||||
|
||||
if (r == null) {
|
||||
throw new IllegalArgumentException("Runnable callback cannot be null");
|
||||
}
|
||||
|
||||
super(minDelay, maxDelay, name);
|
||||
this.clientRunnable = r;
|
||||
this.maxDelay = maxDelay;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
recordInception();
|
||||
this.minDelay = Math.max(MIN_DELAY_FLOOR, minDelay);
|
||||
timer = new Timer(minDelay, e -> timerCallback());
|
||||
timer.setRepeats(false);
|
||||
instances.add(this);
|
||||
@Override
|
||||
protected void swingDoWork() {
|
||||
clientRunnable.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to perform an update. See the class header for the usage of the various
|
||||
* update methods.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void update() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestTime = System.currentTimeMillis();
|
||||
SystemUtilities.runSwingLater(this::checkForWork);
|
||||
Swing.runLater(this::checkForWork);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to perform an update. See the class header for the usage of the various
|
||||
* update methods.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void updateLater() {
|
||||
if (disposed) {
|
||||
return;
|
||||
@@ -174,6 +143,7 @@ public class SwingUpdateManager {
|
||||
* Signals to perform an update. See the class header for the usage of the various
|
||||
* update methods.
|
||||
*/
|
||||
@Override
|
||||
public void updateNow() {
|
||||
synchronized (this) {
|
||||
if (disposed) {
|
||||
@@ -183,176 +153,7 @@ public class SwingUpdateManager {
|
||||
requestTime = System.currentTimeMillis();
|
||||
bufferingStartTime = NONE; // set so that the max delay check will trigger work
|
||||
}
|
||||
SystemUtilities.runSwingNow(this::checkForWork);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to stop any buffered work. This will not stop any in-progress work.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
timer.stop();
|
||||
requestTime = NONE;
|
||||
bufferingStartTime = NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a pending request that hasn't started yet. Any currently
|
||||
* executing requests will not affect this call.
|
||||
*
|
||||
* @return true if there is a pending request that hasn't started yet.
|
||||
*/
|
||||
public synchronized boolean hasPendingUpdates() {
|
||||
if (disposed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return requestTime != NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any work is being performed or if there is buffered work
|
||||
* @return true if any work is being performed or if there is buffered work
|
||||
*/
|
||||
public synchronized boolean isBusy() {
|
||||
if (disposed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return requestTime != NONE || isWorking;
|
||||
}
|
||||
|
||||
public synchronized void dispose() {
|
||||
timer.stop();
|
||||
instances.remove(this);
|
||||
requestTime = NONE;
|
||||
bufferingStartTime = NONE;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
public synchronized boolean isDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " @ " + inceptionInformation;
|
||||
}
|
||||
|
||||
public String toStringDebug() {
|
||||
//@formatter:off
|
||||
return "{\n" +
|
||||
"\tname: " + name + "\n" +
|
||||
"\tcreator: " + inceptionInformation + " ("+System.identityHashCode(this)+")\n" +
|
||||
"\trequest time: "+requestTime + "\n" +
|
||||
"\twork count: " + isWorking + "\n" +
|
||||
"}";
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
// note: this is called on the Swing thread
|
||||
private void checkForWork() {
|
||||
|
||||
if (shouldDoWork()) {
|
||||
doWork();
|
||||
}
|
||||
}
|
||||
|
||||
// This is similar to checkForWork except that it resets the task buffering when
|
||||
// the time expires and there is no work to do.
|
||||
private void timerCallback() {
|
||||
|
||||
if (shouldDoWork()) {
|
||||
doWork();
|
||||
}
|
||||
else if (requestTime == NONE) {
|
||||
bufferingStartTime = NONE; // The timer has fired and there is no pending work
|
||||
}
|
||||
}
|
||||
|
||||
// note: this is called on the Swing thread
|
||||
private synchronized boolean shouldDoWork() {
|
||||
|
||||
// If no pending request, exit without restarting timer
|
||||
if (requestTime == NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (isTimeToWork(now)) {
|
||||
bufferingStartTime = now;
|
||||
requestTime = NONE;
|
||||
isWorking = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
scheduleCheckForWork();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void scheduleCheckForWork() {
|
||||
timer.start();
|
||||
}
|
||||
|
||||
private boolean isTimeToWork(long now) {
|
||||
|
||||
// if past maximum delay, always do work
|
||||
long timeSinceBufferingStart = now - bufferingStartTime;
|
||||
if (timeSinceBufferingStart > maxDelay) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if no new requests have come in since the last time we checked, do work
|
||||
long timeSinceLastRequest = now - requestTime;
|
||||
if (timeSinceLastRequest > minDelay) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// note: this is called on the Swing thread
|
||||
private void doWork() {
|
||||
try {
|
||||
clientRunnable.run();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// catch exceptions so we don't kill the timer
|
||||
Msg.showError(this, null, "Unexpected Exception",
|
||||
"Unexpected exception in Swing Update Manager", t);
|
||||
}
|
||||
|
||||
isWorking = false;
|
||||
|
||||
// we need to clear the buffering flag after the minDelay has passed, so start the timer
|
||||
scheduleCheckForWork();
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inception Info
|
||||
//==================================================================================================
|
||||
|
||||
private void recordInception() {
|
||||
inceptionInformation = getInceptionFromTheFirstClassThatIsNotUs();
|
||||
}
|
||||
|
||||
private String getInceptionFromTheFirstClassThatIsNotUs() {
|
||||
Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(getClass());
|
||||
|
||||
StackTraceElement[] trace = t.getStackTrace();
|
||||
String classInfo = trace[0].toString();
|
||||
|
||||
/*
|
||||
// debug source of creation
|
||||
Throwable filtered = ReflectionUtilities.filterJavaThrowable(t);
|
||||
String string = ReflectionUtilities.stackTraceToString(filtered);
|
||||
classInfo = classInfo + "\n\tfrom:\n\n" + string;
|
||||
*/
|
||||
|
||||
return classInfo;
|
||||
Swing.runLater(this::checkForWork);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.io.Serializable;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.core.*;
|
||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||
import org.apache.logging.log4j.core.config.Property;
|
||||
import org.apache.logging.log4j.core.config.plugins.*;
|
||||
import org.apache.logging.log4j.core.layout.PatternLayout;
|
||||
|
||||
@@ -41,19 +42,20 @@ public class LogPanelAppender extends AbstractAppender {
|
||||
private LogListener logListener;
|
||||
|
||||
protected LogPanelAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
|
||||
super(name, filter, layout);
|
||||
super(name, filter, layout, true, Property.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(LogEvent event) {
|
||||
|
||||
if (logListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// An error is identified as any log that is tagged ERROR or FATAL.
|
||||
boolean isError = event.getLevel().isMoreSpecificThan(Level.ERROR);
|
||||
String message = event.getMessage().getFormattedMessage();
|
||||
|
||||
if (logListener != null) {
|
||||
logListener.messageLogged(message, isError);
|
||||
}
|
||||
logListener.messageLogged(message, isError);
|
||||
}
|
||||
|
||||
@PluginFactory
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.util.task;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public class BufferedSwingRunnerTest extends AbstractGenericTest {
|
||||
|
||||
private static final int MIN_DELAY = 500;
|
||||
private static final int MAX_DELAY = 1000;
|
||||
private volatile int runnableCalled;
|
||||
private BufferedSwingRunner runner = new BufferedSwingRunner(MIN_DELAY, MAX_DELAY);
|
||||
private ClientRunnable runnable = new ClientRunnable();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// must turn this on to get the expected results, as in headless mode the update manager
|
||||
// will run it's Swing work immediately on the test thread, which is not true to the
|
||||
// default behavior
|
||||
System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.FALSE.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneCallForOneRun() {
|
||||
runner.run(runnable);
|
||||
waitForRunner();
|
||||
assertEquals("Expected only 1 callback", 1, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoCallForMultipleFastRunCalls() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
runner.run(runnable);
|
||||
sleep(10);
|
||||
}
|
||||
waitForRunner();
|
||||
assertEquals("Expected 2 callbacks", 2, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneCallForRunLater() {
|
||||
runner.runLater(runnable);
|
||||
waitForRunner();
|
||||
assertEquals("Expected only 1 callback", 1, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxTimeout() {
|
||||
|
||||
//
|
||||
// Hard Test!: Many timing-sensitive factors are at play here: this thread and it's
|
||||
// run and sleep scheduling; the Swing thread and its scheduling and the
|
||||
// Swing timer's scheduling. Thus, making a simple AND reliable test is
|
||||
// nearly impossible. So, we test here the basic max timeout mechanism.
|
||||
//
|
||||
// The loop below calls update many times. The first call to update always
|
||||
// triggers an immediate update. Each successive call, as long as it is
|
||||
// faster than *min delay* will trigger an update to be buffered, to be
|
||||
// delayed. This delay gets reset upon each update call until the *max delay*
|
||||
// time has been passed.
|
||||
//
|
||||
|
||||
int sleepyTime = 5;
|
||||
int maxDelay = 600;
|
||||
runner = new BufferedSwingRunner(200, maxDelay);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
runner.run(runnable);
|
||||
sleep(sleepyTime);
|
||||
}
|
||||
|
||||
assertEquals("Expected 1 max delay callback", 1, runnableCalled);
|
||||
|
||||
waitForRunner();
|
||||
assertEquals("Expected one immediate callback and one max delay callback (2 total)", 2,
|
||||
runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlush() {
|
||||
runner.flush();
|
||||
waitForSwing();
|
||||
assertFalse(runner.isBusy());
|
||||
assertEquals("Did not expect the callback after stop()", 0, runnableCalled);
|
||||
|
||||
runner.runLater(runnable);
|
||||
runner.flush();
|
||||
waitForRunner();
|
||||
assertEquals("Expected only 1 callback", 1, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStop() {
|
||||
runner.runLater(runnable);
|
||||
runner.stop();
|
||||
waitForSwing();
|
||||
assertFalse(runner.isBusy());
|
||||
assertEquals("Did not expect the callback after stop()", 0, runnableCalled);
|
||||
|
||||
//
|
||||
// Make sure we can use again
|
||||
//
|
||||
runnableCalled = 0;
|
||||
runner.run(runnable);
|
||||
waitForRunner();
|
||||
assertEquals("Expected only 1 callback", 1, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasPendingUpdates() {
|
||||
runner.runLater(runnable);
|
||||
assertTrue("Should have pending updates after calling update, but before the work" +
|
||||
"has been done", runner.hasPendingUpdates());
|
||||
waitForRunner();
|
||||
assertEquals("Expected only 1 callback", 1, runnableCalled);
|
||||
assertFalse("Still have pending updates after performing work",
|
||||
runner.hasPendingUpdates());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDispose() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
runner.run(runnable);
|
||||
sleep(1);
|
||||
}
|
||||
assertTrue(runner.hasPendingUpdates());
|
||||
runner.dispose();
|
||||
assertFalse(runner.hasPendingUpdates());
|
||||
int called = runnableCalled;
|
||||
//
|
||||
// Cannot use again
|
||||
//
|
||||
runner.run(runnable);
|
||||
waitForRunner();
|
||||
// make sure the callback did not occur.
|
||||
assertEquals(called, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBusy() throws Exception {
|
||||
//
|
||||
// Another complicated test: we want to make sure that if we try to trigger an update
|
||||
// that isBusy() will return true, even if the update runnable has not yet been posted
|
||||
// to the Swing thread.
|
||||
//
|
||||
// To do this, we will need to block the Swing thread so that we know the request happens
|
||||
// before the update runnable is called.
|
||||
//
|
||||
|
||||
CountDownLatch startLatch = new CountDownLatch(1);
|
||||
CountDownLatch endLatch = new CountDownLatch(1);
|
||||
AtomicBoolean exception = new AtomicBoolean();
|
||||
|
||||
runSwing(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startLatch.countDown();
|
||||
try {
|
||||
endLatch.await(10, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
exception.set(true);
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
// This will cause the swing thread to block until we countdown the end latch
|
||||
startLatch.await(10, TimeUnit.SECONDS);
|
||||
|
||||
runner.run(runnable);
|
||||
assertTrue("Manager not busy after requesting an update", runner.isBusy());
|
||||
|
||||
endLatch.countDown();
|
||||
|
||||
waitForRunner();
|
||||
assertFalse("Manager still busy after waiting for update", runner.isBusy());
|
||||
|
||||
assertFalse("Interrupted waiting for CountDowLatch", exception.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallToUpdateWhileAnUpdateIsWorking() throws Exception {
|
||||
|
||||
//
|
||||
// Test that an update call from a non-swing thread will still get processed if the
|
||||
// manager is actively processing an update on the swing thread.
|
||||
//
|
||||
|
||||
CountDownLatch startLatch = new CountDownLatch(1);
|
||||
CountDownLatch endLatch = new CountDownLatch(1);
|
||||
AtomicBoolean exception = new AtomicBoolean();
|
||||
|
||||
Runnable r = () -> {
|
||||
runnableCalled++;
|
||||
|
||||
startLatch.countDown();
|
||||
try {
|
||||
endLatch.await(10, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
exception.set(true);
|
||||
}
|
||||
};
|
||||
|
||||
// start the update manager and have it wait for us
|
||||
runner.run(r);
|
||||
|
||||
// have the swing thread block until we countdown the end latch
|
||||
startLatch.await(10, TimeUnit.SECONDS);
|
||||
|
||||
// post the second update request now that the manager is actively processing
|
||||
runner.run(runnable);
|
||||
|
||||
// let the update manager finish; verify 2 work items total
|
||||
endLatch.countDown();
|
||||
waitForRunner();
|
||||
assertEquals("Expected exactly 2 callbacks", 2, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotFiringTooOften() throws InterruptedException {
|
||||
Thread t = new Thread(() -> {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
runner.run(runnable);
|
||||
runner.run(runnable);
|
||||
runner.run(runnable);
|
||||
runner.run(runnable);
|
||||
sleep(10);
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
t.join();
|
||||
waitForRunner();
|
||||
assertEquals(2, runnableCalled);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
private void waitForRunner() {
|
||||
|
||||
// let all swing updates finish, which may trigger the update manager
|
||||
waitForSwing();
|
||||
|
||||
while (runner.isBusy()) {
|
||||
sleep(DEFAULT_WAIT_DELAY);
|
||||
}
|
||||
|
||||
// let any resulting swing events finish
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private class ClientRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
runnableCalled++;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
-5
@@ -25,7 +25,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public class SwingUpdateManagerTest extends AbstractGenericTest {
|
||||
@@ -52,7 +51,7 @@ public class SwingUpdateManagerTest extends AbstractGenericTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneCallFroUpdateNow() {
|
||||
public void testOneCallFromUpdateNow() {
|
||||
manager.updateNow();
|
||||
waitForManager();
|
||||
assertEquals("Expected only 1 callback", 1, runnableCalled);
|
||||
@@ -107,6 +106,19 @@ public class SwingUpdateManagerTest extends AbstractGenericTest {
|
||||
runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlush() {
|
||||
manager.flush();
|
||||
waitForSwing();
|
||||
assertFalse(manager.isBusy());
|
||||
assertEquals("Did not expect the callback after stop()", 0, runnableCalled);
|
||||
|
||||
manager.updateLater();
|
||||
manager.flush();
|
||||
waitForManager();
|
||||
assertEquals("Expected only 1 callback", 1, runnableCalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStop() {
|
||||
manager.updateLater();
|
||||
@@ -236,9 +248,26 @@ public class SwingUpdateManagerTest extends AbstractGenericTest {
|
||||
assertEquals("Expected exactly 2 callbacks", 2, runnableCalled);
|
||||
}
|
||||
|
||||
//==============================================================================================
|
||||
@Test
|
||||
public void testNotFiringTooOften() throws InterruptedException {
|
||||
Thread t = new Thread(() -> {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
manager.update();
|
||||
manager.update();
|
||||
manager.update();
|
||||
manager.update();
|
||||
sleep(10);
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
t.join();
|
||||
waitForManager();
|
||||
assertEquals(2, runnableCalled);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==============================================================================================
|
||||
//==================================================================================================
|
||||
private void waitForManager() {
|
||||
|
||||
// let all swing updates finish, which may trigger the update manager
|
||||
@@ -258,7 +287,6 @@ public class SwingUpdateManagerTest extends AbstractGenericTest {
|
||||
@Override
|
||||
public void run() {
|
||||
runnableCalled++;
|
||||
Msg.debug(this, "run() called - count: " + runnableCalled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
/* ###
|
||||
* 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 org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public class SwingUpdateManagerTimerTest extends AbstractGenericTest {
|
||||
private static final int MIN_DELAY = 100;
|
||||
private static final int MAX_DELAY = 10000;
|
||||
private volatile int runnableCalled;
|
||||
private SwingUpdateManager manager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
manager = createUpdateManager(MIN_DELAY, MAX_DELAY);
|
||||
|
||||
// must turn this on to get the expected results, as in headless mode the update manager
|
||||
// will run it's Swing work immediately on the test thread, which is not true to the
|
||||
// default behavior
|
||||
System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.FALSE.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotFiringTooOften() throws InterruptedException {
|
||||
Thread t = new Thread(() -> {
|
||||
for (int i = 0; i < 50; i++) {
|
||||
manager.update();
|
||||
manager.update();
|
||||
manager.update();
|
||||
manager.update();
|
||||
sleep(10);
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
t.join();
|
||||
waitForManager();
|
||||
assertEquals(2, runnableCalled);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
private void waitForManager() {
|
||||
|
||||
// let all swing updates finish, which may trigger the update manager
|
||||
waitForSwing();
|
||||
|
||||
while (manager.isBusy()) {
|
||||
sleep(DEFAULT_WAIT_DELAY);
|
||||
}
|
||||
|
||||
// let any resulting swing events finish
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private SwingUpdateManager createUpdateManager(int min, int max) {
|
||||
return new SwingUpdateManager(min, max, "bob", new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runnableCalled++;
|
||||
Msg.debug(this, "run() called - count: " + runnableCalled);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,10 @@ import docking.help.Help;
|
||||
import docking.help.HelpService;
|
||||
import docking.widgets.EmptyBorderButton;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.layout.HorizontalLayout;
|
||||
import ghidra.util.task.BufferedSwingRunner;
|
||||
import log.LogListener;
|
||||
import log.LogPanelAppender;
|
||||
import resources.ResourceManager;
|
||||
@@ -45,6 +47,8 @@ public class LogPanel extends JPanel implements LogListener {
|
||||
private JLabel label;
|
||||
private Color defaultColor;
|
||||
|
||||
private BufferedSwingRunner messageUpdater = new BufferedSwingRunner();
|
||||
|
||||
LogPanel(final FrontEndPlugin plugin) {
|
||||
super(new BorderLayout());
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
@@ -89,13 +93,12 @@ public class LogPanel extends JPanel implements LogListener {
|
||||
|
||||
@Override
|
||||
public void messageLogged(String message, boolean isError) {
|
||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
||||
label.setForeground(defaultColor);
|
||||
if (isError) {
|
||||
label.setForeground(Color.RED);
|
||||
}
|
||||
label.setText(message);
|
||||
label.setToolTipText(message);
|
||||
|
||||
messageUpdater.run(() -> {
|
||||
label.setForeground(isError ? Color.RED : defaultColor);
|
||||
String text = message.replace("\n", " ");
|
||||
label.setText(text);
|
||||
label.setToolTipText(text);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,4 +118,5 @@ public class LogPanel extends JPanel implements LogListener {
|
||||
|
||||
logAppender.setLogListener(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user