mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-2040: Remove garbage from AsyncUtils.
This commit is contained in:
parent
3d7089d391
commit
922c4d0186
40 changed files with 271 additions and 5277 deletions
|
@ -21,6 +21,7 @@ import java.util.*;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
|
@ -32,7 +33,8 @@ import ghidra.app.plugin.core.debug.gui.tracermi.RemoteMethodInvocationDialog;
|
|||
import ghidra.app.plugin.core.debug.service.target.AbstractTarget;
|
||||
import ghidra.app.services.DebuggerConsoleService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.async.*;
|
||||
import ghidra.async.AsyncFence;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.debug.api.ValStr;
|
||||
import ghidra.debug.api.model.DebuggerObjectActionContext;
|
||||
import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext;
|
||||
|
@ -1086,9 +1088,11 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
|
||||
@Override
|
||||
public CompletableFuture<Void> readMemoryAsync(AddressSetView set, TaskMonitor monitor) {
|
||||
// I still separate into blocks, because I want user to be able to cancel
|
||||
// NOTE: I don't intend to warn about the number of requests.
|
||||
// They're delivered in serial, and there's a cancel button that works
|
||||
/**
|
||||
* I still separate into blocks, because I want user to be able to cancel. I don't intend to
|
||||
* warn about the number of requests. They're delivered in serial, and there's a cancel
|
||||
* button that works
|
||||
*/
|
||||
|
||||
MatchedMethod readMem =
|
||||
matches.getBest(ReadMemMatcher.class, null, ActionName.READ_MEM, ReadMemMatcher.ALL);
|
||||
|
@ -1113,24 +1117,28 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
}
|
||||
monitor.initialize(total);
|
||||
monitor.setMessage("Reading memory");
|
||||
// NOTE: Don't read in parallel, lest we overload the connection
|
||||
return AsyncUtils.each(TypeSpec.VOID, quantized.iterator(), (r, loop) -> {
|
||||
AddressRangeChunker blocks = new AddressRangeChunker(r, BLOCK_SIZE);
|
||||
|
||||
/**
|
||||
* NOTE: Don't read in parallel, lest we overload the connection. This does queue them all
|
||||
* up in a CF chain, though. Still, a request doesn't go out until the preceding one
|
||||
* completes.
|
||||
*/
|
||||
return quantized.stream().flatMap(r -> {
|
||||
if (r.getAddressSpace().isRegisterSpace()) {
|
||||
Msg.warn(this, "Request to read registers via readMemory: " + r + ". Ignoring.");
|
||||
loop.repeatWhile(!monitor.isCancelled());
|
||||
return;
|
||||
return Stream.of();
|
||||
}
|
||||
return new AddressRangeChunker(r, BLOCK_SIZE).stream();
|
||||
}).reduce(AsyncUtils.nil(), (f, blk) -> {
|
||||
if (monitor.isCancelled()) {
|
||||
return f;
|
||||
}
|
||||
AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (blk, inner) -> {
|
||||
monitor.incrementProgress(1);
|
||||
final Map<String, Object> args;
|
||||
if (paramProcess != null) {
|
||||
TraceObject process = procsBySpace.computeIfAbsent(blk.getAddressSpace(),
|
||||
this::getProcessForSpace);
|
||||
if (process == null) {
|
||||
Msg.warn(this, "Cannot find process containing " + blk.getMinAddress());
|
||||
inner.repeatWhile(!monitor.isCancelled());
|
||||
return;
|
||||
return f;
|
||||
}
|
||||
args = Map.ofEntries(
|
||||
Map.entry(paramProcess.name(), process),
|
||||
|
@ -1140,13 +1148,19 @@ public class TraceRmiTarget extends AbstractTarget {
|
|||
args = Map.ofEntries(
|
||||
Map.entry(paramRange.name(), blk));
|
||||
}
|
||||
CompletableFuture<Void> future =
|
||||
requestCaches.readBlock(blk.getMinAddress(), readMem.method, args);
|
||||
future.exceptionally(e -> {
|
||||
|
||||
return f.thenComposeAsync(__ -> {
|
||||
if (monitor.isCancelled()) {
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
monitor.incrementProgress(1);
|
||||
return requestCaches.readBlock(blk.getMinAddress(), readMem.method, args);
|
||||
}, AsyncUtils.FRAMEWORK_EXECUTOR).exceptionally(e -> {
|
||||
Msg.error(this, "Could not read " + blk + ": " + e);
|
||||
return null; // Continue looping on errors
|
||||
}).thenApply(__ -> !monitor.isCancelled()).handle(inner::repeatWhile);
|
||||
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
|
||||
});
|
||||
}, (f1, f2) -> {
|
||||
throw new AssertionError("Should be sequential");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,109 +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.async;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A queue of claims on an associated event
|
||||
*
|
||||
* Often a single event handler processes all events of a given type or from a given source. This
|
||||
* class offers a means of "claiming" an event, so that it can be processed by a specific handler.
|
||||
* The general handler must call the {@link #satisfy(Object)} method, foregoing its usual handling
|
||||
* if it returns true.
|
||||
*
|
||||
* This permits other threads to claim the next (unclaimed) event from the source. This is
|
||||
* particularly useful when the claimant knows it will also be the cause of the event, allowing it
|
||||
* to override the usual event processing with its own. Care must be taken to claim the event before
|
||||
* performing the action that will cause the event, otherwise a race condition arises wherein the
|
||||
* caused event may occur before the causer has claimed it.
|
||||
*
|
||||
* @param <T> the type of the event
|
||||
*/
|
||||
public class AsyncClaimQueue<T> {
|
||||
private static class Entry<T> {
|
||||
final CompletableFuture<T> future;
|
||||
final Predicate<T> predicate;
|
||||
|
||||
Entry(CompletableFuture<T> future, Predicate<T> predicate) {
|
||||
this.future = future;
|
||||
this.predicate = predicate;
|
||||
}
|
||||
}
|
||||
|
||||
private final Deque<Entry<T>> queue = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Claim the next unclaimed occurrence of the event.
|
||||
*
|
||||
* @return a future which completes with the claimed occurrence
|
||||
*/
|
||||
public CompletableFuture<T> claim() {
|
||||
return claim(t -> true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim, upon a predicate, the next unclaimed occurrence of the event.
|
||||
*
|
||||
* If the occurrence does not satisfy the predicate, the next claim in the queue is tried, if
|
||||
* present.
|
||||
*
|
||||
* NOTE: The predicate should be fast and non-blocking, since it is executed while holding the
|
||||
* lock to the internal queue.
|
||||
*
|
||||
* @param predicate a condition upon which to claim the occurrence
|
||||
* @return a future which completes with the claimed occurrence
|
||||
*/
|
||||
public CompletableFuture<T> claim(Predicate<T> predicate) {
|
||||
synchronized (queue) {
|
||||
CompletableFuture<T> future = new CompletableFuture<T>();
|
||||
queue.add(new Entry<>(future, predicate));
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a claimant, if any, of the occurrence of the event.
|
||||
*
|
||||
* This method should be called by the usual handler for every occurrence the event of interest.
|
||||
* If the occurrence is claimed, the claimant is notified, and true is returned. If the
|
||||
* occurrence is free, false is returned, and the handler should proceed as usual.
|
||||
*
|
||||
* @param t the event occurrence
|
||||
* @return true if claimed, false otherwise
|
||||
*/
|
||||
public boolean satisfy(T t) {
|
||||
Entry<T> entry = null;
|
||||
synchronized (queue) {
|
||||
Iterator<Entry<T>> eit = queue.iterator();
|
||||
while (eit.hasNext()) {
|
||||
Entry<T> e = eit.next();
|
||||
if (e.predicate.test(t)) {
|
||||
entry = e;
|
||||
eit.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry == null) {
|
||||
return false;
|
||||
}
|
||||
entry.future.complete(t);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ public class AsyncFence {
|
|||
* Calling this method after {@link #ready()} will yield undefined results.
|
||||
*
|
||||
* @param future the participant to add
|
||||
* @return this fence
|
||||
*/
|
||||
public synchronized AsyncFence include(CompletableFuture<?> future) {
|
||||
if (ready != null) {
|
||||
|
@ -84,11 +85,9 @@ public class AsyncFence {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Diagnostic: Get the participants which have not yet completed
|
||||
*
|
||||
* Diagnostic
|
||||
*
|
||||
* @return
|
||||
* @return the pending participants
|
||||
*/
|
||||
public Set<CompletableFuture<?>> getPending() {
|
||||
return participants.stream().filter(f -> !f.isDone()).collect(Collectors.toSet());
|
||||
|
|
|
@ -1,67 +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.async;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Methods for exiting a chain common to all handlers
|
||||
*
|
||||
* @param <R> the type of result for the whole construct
|
||||
*/
|
||||
public interface AsyncHandlerCanExit<R> {
|
||||
/**
|
||||
* Complete the whole loop
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)}. While it can be invoked directly, consider the
|
||||
* convenience methods {@link #exit(Object)} and {@link #exit(Throwable)} instead.
|
||||
*
|
||||
* When the subordinate completes, the whole construct completes and terminates with the same,
|
||||
* possibly exceptional, result.
|
||||
*
|
||||
* @param result the result of completion
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return
|
||||
*/
|
||||
public Void exit(R result, Throwable exc);
|
||||
|
||||
/**
|
||||
* Complete the chain with the given result
|
||||
*
|
||||
* @param result the result
|
||||
*/
|
||||
public default void exit(R result) {
|
||||
exit(result, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the chain with {@code null}
|
||||
*/
|
||||
public default void exit() {
|
||||
exit(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the chain exceptionally
|
||||
*
|
||||
* @param exc the exception
|
||||
*/
|
||||
public default void exit(Throwable exc) {
|
||||
exit(null, exc);
|
||||
}
|
||||
}
|
|
@ -171,7 +171,7 @@ public class AsyncLazyMap<K, V> {
|
|||
* <p>
|
||||
* This will replace the behavior of any previous value-testing predicate.
|
||||
*
|
||||
* @param predicate
|
||||
* @param predicate the rule for forgetting entries
|
||||
* @return this lazy map
|
||||
*/
|
||||
public synchronized AsyncLazyMap<K, V> forgetValues(
|
||||
|
@ -184,7 +184,7 @@ public class AsyncLazyMap<K, V> {
|
|||
* Sets a predicate to determine which values to remember
|
||||
*
|
||||
* @see #forgetValues(BiPredicate)
|
||||
* @param predicate
|
||||
* @param predicate the rule for <em>not</em> forgetting entries
|
||||
* @return this lazy map
|
||||
*/
|
||||
public synchronized AsyncLazyMap<K, V> rememberValues(
|
||||
|
@ -322,6 +322,7 @@ public class AsyncLazyMap<K, V> {
|
|||
* Remove a key from the map, canceling any pending computation
|
||||
*
|
||||
* @param key the key to remove
|
||||
* @return the previous value, if completed
|
||||
*/
|
||||
public V remove(K key) {
|
||||
KeyedFuture<K, V> f;
|
||||
|
|
|
@ -1,335 +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.async;
|
||||
|
||||
import static ghidra.async.AsyncUtils.sequence;
|
||||
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import ghidra.async.seq.AsyncSequenceWithTemp;
|
||||
import ghidra.async.seq.AsyncSequenceWithoutTemp;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* An optionally-reentrant lock for managing a contentious resource
|
||||
*
|
||||
* <p>
|
||||
* Typically, a resource has a queue of actions that it executes in order, or it may only permit a
|
||||
* single pending action at any given time. If a task composed of many actions needs to ensure no
|
||||
* other actions are queued in between, it must use a lock. This is analogous to thread
|
||||
* synchronization. This can also be described as a queue to enter other queues.
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* public CompletableFuture<Void> exampleLock1() {
|
||||
* AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
|
||||
* return seq(TypeSpec.VOID).then((seq) -> {
|
||||
* lock.acquire(null).handle(seq::next);
|
||||
* }, hold).then((seq) -> {
|
||||
* doCriticalStuff().handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* doMoreCriticalStuff().handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* hold.release();
|
||||
* seq.exit();
|
||||
* }).asCompletableFuture().exceptionally((exc) -> {
|
||||
* hold.release();
|
||||
* return ExceptionUtils.rethrow(exc);
|
||||
* });
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Or more succinctly:
|
||||
*
|
||||
* <pre>
|
||||
* public CompletableFuture<Void> exampleLock2() {
|
||||
* return lock.with(TypeSpec.VOID, null).then((hold, seq) -> {
|
||||
* doCriticalStuff().handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* doMoreCriticalStuff().handle(seq::next);
|
||||
* }).asCompletableFuture();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Re-entry is supported via a {@link Hold}. A method that offers re-enter into a critical section
|
||||
* must accept a {@link Hold} parameter. Generally, the caller indicates the callee should reenter
|
||||
* by passing the current hold. It then forwards it to the lock, via {@link #acquire(Hold)} or
|
||||
* {@link #with(TypeSpec, Hold)}. The hold must be a reference to the current hold or null. A hold
|
||||
* from another lock cannot be used for re-entry. If it is null, normal behavior is applied and the
|
||||
* queue is served in FIFO order. If it is a reference to the current hold, the callback is executed
|
||||
* immediately. Reentrant holds must be released in the reverse order of acquisition.
|
||||
*
|
||||
* <pre>
|
||||
* public CompletableFuture<Void> canReenter(Hold reenter) {
|
||||
* return lock.with(TypeSpec.VOID, reenter).then((hold, seq) -> {
|
||||
* doCriticalStuff();
|
||||
* }).asCompletableFuture();
|
||||
* }
|
||||
*
|
||||
* public CompletableFuture<Void> exampleLock3() {
|
||||
* return lock.with(TypeSpec.VOID, null).then((hold, seq) -> {
|
||||
* canReenter(hold).handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* doMoreCriticalStuff().handle(seq::next);
|
||||
* }).asCompletableFuture();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The implementation is based on a queue a queue of futures. The {@link #acquire(Hold)} task
|
||||
* completes when the lock is available or if re-entry is possible. {@link Hold#release()} is not a
|
||||
* task, but it must be called to ensure the next handler is invoked. If a hold is forgotten, e.g.,
|
||||
* a sequence fails to release it, deadlock will likely occur. In some circumstances, deadlock is
|
||||
* detected, causing all queued actions (current and future) to complete exceptionally with an
|
||||
* {@link IllegalStateException}. Deadlock detection is a debugging feature. A programmer cannot
|
||||
* rely on it for error recovery. If the exception is thrown, it indicates a serious flaw in the
|
||||
* program which cannot be corrected at runtime.
|
||||
*
|
||||
* <p>
|
||||
* The above examples demonstrate two critical actions, because in general, a single action is
|
||||
* atomic. This lock <em>does not</em> protect against the usual multi-threaded hazards. Because any
|
||||
* queue may be served by a multi-threaded executor, shared resources must be protected using
|
||||
* standard synchronization primitives, e.g., the {@code synchronized} keyword. Resources whose
|
||||
* methods provide futures are better protected using this lock, because a standard {@link Lock}
|
||||
* will block the calling thread -- perhaps stalling a queue's executor -- whereas this lock permits
|
||||
* the thread to execute other actions.
|
||||
*
|
||||
* @note This implementation offers little protection against double locking, or gratuitous
|
||||
* releasing.
|
||||
* @note As an asynchronous task, {@link #acquire()} returns immediately, but the future does not
|
||||
* complete until the lock is acquired.
|
||||
*/
|
||||
public class AsyncLock {
|
||||
private class HoldState implements Runnable {
|
||||
boolean released = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!released) {
|
||||
Msg.error(this, "Some poor soul forgot to release a lock. Now, it's dead!");
|
||||
dead = true;
|
||||
List<CompletableFuture<?>> copy;
|
||||
synchronized (AsyncLock.this) {
|
||||
copy = List.copyOf(queue);
|
||||
queue.clear();
|
||||
}
|
||||
for (CompletableFuture<?> future : copy) {
|
||||
future.completeExceptionally(new IllegalStateException("This lock is dead! " +
|
||||
"I.e., an ownership token became phantom reachable without first being " +
|
||||
"released"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Deque<CompletableFuture<Hold>> queue = new LinkedList<>();
|
||||
protected WeakReference<Hold> curHold;
|
||||
protected int reentries = 0;
|
||||
protected Throwable disposalReason;
|
||||
protected boolean dead = false;
|
||||
protected final String debugName;
|
||||
|
||||
/**
|
||||
* An opaque lock ownership handle
|
||||
*/
|
||||
public class Hold {
|
||||
final HoldState state = new HoldState();
|
||||
final Cleanable cleanable;
|
||||
|
||||
private Hold() {
|
||||
cleanable = AsyncUtils.CLEANER.register(this, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release ownership of the associated lock
|
||||
*/
|
||||
public void release() {
|
||||
debug(this + ".release()");
|
||||
CompletableFuture<Hold> next;
|
||||
Hold oldHold = null;
|
||||
synchronized (AsyncLock.this) {
|
||||
oldHold = curHold.get();
|
||||
if (this != oldHold) {
|
||||
Msg.error(this, "Invalid ownership handle: " + oldHold + " != " + this);
|
||||
throw new IllegalStateException("Invalid ownership handle");
|
||||
}
|
||||
if (reentries > 0) {
|
||||
debug(" is from reentry");
|
||||
reentries--;
|
||||
return;
|
||||
}
|
||||
debug(" is non-reentrant release");
|
||||
state.released = true;
|
||||
next = queue.poll();
|
||||
if (next != null) {
|
||||
debug(" has queued waiters");
|
||||
Hold newHold = new Hold();
|
||||
curHold = new WeakReference<>(newHold);
|
||||
debug(" launching next, granting " + newHold);
|
||||
// Use completeAsync, since I'm inside synchronized block
|
||||
next.completeAsync(() -> newHold);
|
||||
return;
|
||||
}
|
||||
debug(" has no waiters");
|
||||
curHold = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new lock
|
||||
*/
|
||||
public AsyncLock() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a lock with debug printing
|
||||
*
|
||||
* <p>
|
||||
* This lock will print calls to {@link #acquire(Hold)} and {@link Hold#release()}. It will also
|
||||
* note when the lock is acquired or re-entered, printing the current hold.
|
||||
*
|
||||
* @param debugName a name to prefix to debug messages
|
||||
*/
|
||||
public AsyncLock(String debugName) {
|
||||
this.debugName = debugName;
|
||||
}
|
||||
|
||||
private void debug(String msg) {
|
||||
if (debugName != null) {
|
||||
Msg.debug(this, "LOCK: " + debugName + ": " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a future on this lock, possibly re-entering
|
||||
*
|
||||
* <p>
|
||||
* If reentry is {@code null}, then this will acquire the lock without reentry. Otherwise, the
|
||||
* lock checks the provided hold. If it is valid, the lock is immediately acquired via re-entry.
|
||||
* If it is not valid, an exception is thrown.
|
||||
*
|
||||
* @param reentry a hold to prove current lock ownership for reentry
|
||||
* @return a future that completes when the lock is held
|
||||
* @throws IllegalStateException if the given reentry hold is not the current hold on this lock
|
||||
*/
|
||||
public CompletableFuture<Hold> acquire(Hold reentry) {
|
||||
debug(".acquire(" + reentry + ")");
|
||||
Hold strongHold = null;
|
||||
synchronized (this) {
|
||||
if (disposalReason != null) {
|
||||
return CompletableFuture.failedFuture(disposalReason);
|
||||
}
|
||||
if (dead) {
|
||||
throw new IllegalStateException("This lock is dead! " +
|
||||
"I.e., an ownership token was finalized without first being released");
|
||||
}
|
||||
if (reentry == null && curHold != null) {
|
||||
debug(" is held: queuing");
|
||||
CompletableFuture<Hold> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
return future;
|
||||
}
|
||||
if (reentry == null && curHold == null) {
|
||||
strongHold = new Hold();
|
||||
debug(" is available: granting " + strongHold);
|
||||
curHold = new WeakReference<>(strongHold);
|
||||
return CompletableFuture.completedFuture(strongHold);
|
||||
}
|
||||
if (reentry.state.released) {
|
||||
throw new IllegalStateException("Reentrant hold is released");
|
||||
}
|
||||
if (reentry == curHold.get()) {
|
||||
debug(" is held by requester: reentering");
|
||||
reentries++;
|
||||
return CompletableFuture.completedFuture(reentry);
|
||||
}
|
||||
// TODO: This might actually be an internal error.
|
||||
// I can't think of a situation where this could occur by API misuse.
|
||||
throw new IllegalStateException("Reentrant hold is not the current hold");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a sequence of actions on this lock
|
||||
*
|
||||
* <p>
|
||||
* The lock will be acquired before executing the first action of the sequence, and the hold
|
||||
* will be automatically released upon completion, whether normal or exceptional. The first
|
||||
* action receives a reference to the hold, which may be used to re-enter the lock.
|
||||
*
|
||||
* <p>
|
||||
* If the sequence stalls, i.e., an action never completes, it will cause deadlock.
|
||||
*
|
||||
* @param type the type "returned" by the sequence
|
||||
* @param hold an optional handle to prove current ownership for re-entry
|
||||
* @return a sequence of actions wrapped by lock acquisition and release
|
||||
*/
|
||||
public <R> AsyncSequenceWithTemp<R, Hold> with(TypeSpec<R> type, Hold hold) {
|
||||
AtomicReference<Hold> handle = new AtomicReference<>();
|
||||
return with(type, hold, handle).then((seq) -> {
|
||||
seq.next(handle.get(), null);
|
||||
}, TypeSpec.cls(Hold.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a sequence of actions on this lock
|
||||
*
|
||||
* <p>
|
||||
* Identical to {@link #with(TypeSpec, Hold)} except that the acquired hold is stored into an
|
||||
* atomic reference rather than passed to the first action.
|
||||
*
|
||||
* @param type the type "returned" by the sequence
|
||||
* @param hold an optional hold to prove current ownership for re-entry
|
||||
* @param handle an atomic reference to store the hold
|
||||
* @see #with(TypeSpec, Hold)
|
||||
* @return a sequence of actions wrapped by lock acquisition and release
|
||||
*/
|
||||
public <R> AsyncSequenceWithoutTemp<R> with(TypeSpec<R> type, Hold hold,
|
||||
AtomicReference<Hold> handle) {
|
||||
return sequence(type).then((seq) -> {
|
||||
acquire(hold).handle(seq::next);
|
||||
}, handle).onExit((result, exc) -> {
|
||||
handle.get().release();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy this lock, causing all pending actions to complete exceptionally
|
||||
*/
|
||||
public void dispose(Throwable reason) {
|
||||
List<CompletableFuture<?>> copy;
|
||||
synchronized (this) {
|
||||
disposalReason = reason;
|
||||
copy = List.copyOf(queue);
|
||||
queue.clear();
|
||||
}
|
||||
for (CompletableFuture<?> future : copy) {
|
||||
future.completeExceptionally(reason);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,167 +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.async;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A cache of futures which pairs each to its result by key
|
||||
*
|
||||
* <p>
|
||||
* The cache accepts promises and results, storing unpaired entries for a timeout period. Each
|
||||
* promise is fulfilled when the cache accepts its corresponding result, determined by key.
|
||||
* Conversely, a cached result fulfills its corresponding promise, determined by key, when the cache
|
||||
* accepts that promise. Thus, the cache is two sided. When both the promise and the result for a
|
||||
* given key enter the cache, they are paired, the promise is fulfilled, and both are removed from
|
||||
* the cache.
|
||||
*
|
||||
* <p>
|
||||
* If an entry is not paired within the timeout period, it is evicted. An evicted promise is likely
|
||||
* a recoverable error, e.g., a request timed out. An evicted result is likely a logic or
|
||||
* synchronization error. Requests, i.e., promises, are usually created before the result is
|
||||
* obtained. A result may enter the cache first due to variance in execution order, but the promise
|
||||
* usually enters soon after. If not, more than likely, the developer forgot to enter the request
|
||||
* into the cache, or if the result was reported out of band, it is gratuitous. Callbacks are
|
||||
* provided for eviction from either side of the cache.
|
||||
*
|
||||
* @param <K> the type of keys
|
||||
* @param <V> the type of result values
|
||||
*/
|
||||
public abstract class AsyncPairingCache<K, V> {
|
||||
private final Map<K, V> results;
|
||||
private final Map<K, V> resultsView;
|
||||
|
||||
private final Map<K, CompletableFuture<V>> promises;
|
||||
private final Map<K, CompletableFuture<V>> promisesView;
|
||||
|
||||
/**
|
||||
* Construct a new matching cache
|
||||
*
|
||||
* @param maxPending the maximum number of pending promises or results before the eldest is
|
||||
* evicted. Each is counted independently, e.g., a value of 5 permits 5 pending
|
||||
* promises and 5 pending results simultaneously.
|
||||
*/
|
||||
public AsyncPairingCache(int maxPending) {
|
||||
results = createResultCache(maxPending);
|
||||
resultsView = Collections.unmodifiableMap(results);
|
||||
|
||||
promises = createPromiseCache(maxPending);
|
||||
promisesView = Collections.unmodifiableMap(promises);
|
||||
}
|
||||
|
||||
protected abstract Map<K, V> createResultCache(int max);
|
||||
|
||||
protected abstract Map<K, CompletableFuture<V>> createPromiseCache(int max);
|
||||
|
||||
/**
|
||||
* Enter a promise for the given key into the cache
|
||||
*
|
||||
* <p>
|
||||
* If the result for the given key is already available, the promise does not enter the cache.
|
||||
* Instead, the result is removed and the promise is completed.
|
||||
*
|
||||
* @param key the key for the expected result
|
||||
* @param promise the promise to be completed when the result is available
|
||||
*/
|
||||
public CompletableFuture<V> waitOn(K key) {
|
||||
return waitOn(key, k -> new CompletableFuture<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter a promise for the given key into the cache
|
||||
*
|
||||
* <p>
|
||||
* If the result for the given key is already available, the promise does not enter the cache.
|
||||
* Instead, the result is removed and the promise is completed.
|
||||
*
|
||||
* @param key the key for the expected result
|
||||
* @param promise the promise to be completed when the result is available
|
||||
*/
|
||||
public CompletableFuture<V> waitOn(K key, Function<K, CompletableFuture<V>> futureFactory) {
|
||||
V value;
|
||||
synchronized (this) {
|
||||
value = results.remove(key);
|
||||
if (value == null) {
|
||||
return promises.computeIfAbsent(key, futureFactory);
|
||||
}
|
||||
}
|
||||
CompletableFuture<V> future = futureFactory.apply(key);
|
||||
future.complete(value);
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter a result for the given key into the cache
|
||||
*
|
||||
* <p>
|
||||
* If a promise for the key already exists, the result does not enter the cache. Instead, the
|
||||
* promise is removed and completed.
|
||||
*
|
||||
* @param key the key for the provided result
|
||||
* @param value the result
|
||||
*/
|
||||
public void fulfill(K key, V value) {
|
||||
CompletableFuture<V> promise;
|
||||
synchronized (this) {
|
||||
promise = promises.remove(key);
|
||||
if (promise == null) {
|
||||
results.put(key, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
promise.complete(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the cache, completing all pending requests exceptionally
|
||||
*
|
||||
* <p>
|
||||
* Both sides of the cache are cleared.
|
||||
*
|
||||
* @param exc the exception for completing the requests
|
||||
*/
|
||||
public void flush(Throwable exc) {
|
||||
Set<CompletableFuture<V>> copy = new HashSet<>();
|
||||
synchronized (this) {
|
||||
copy.addAll(promises.values());
|
||||
promises.clear();
|
||||
results.clear();
|
||||
}
|
||||
for (CompletableFuture<V> p : copy) {
|
||||
p.completeExceptionally(exc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map view of unpaired promises
|
||||
*
|
||||
* @return the map
|
||||
*/
|
||||
public Map<K, CompletableFuture<V>> getUnpairedPromises() {
|
||||
return promisesView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map view of unpaired results
|
||||
*
|
||||
* @return the map
|
||||
*/
|
||||
public Map<K, V> getUnpairedResults() {
|
||||
return resultsView;
|
||||
}
|
||||
}
|
|
@ -1,78 +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.async;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A race which orders futures by completion time
|
||||
*
|
||||
* This is roughly equivalent to Java's
|
||||
* {@link CompletableFuture#acceptEither(CompletionStage, Consumer)} or
|
||||
* {@link CompletableFuture#anyOf(CompletableFuture...)}; however, it is more general. Many futures
|
||||
* may participate in the race. Each call to {@link #include(CompletableFuture)} adds a participant
|
||||
* to the race. A call to {@link #next()} returns a future which completes when the first
|
||||
* participant completes. This subsumes and provides a shorthand for the standard methods. A second
|
||||
* call to {@link #next()} returns a future which completes when the second participant completes,
|
||||
* and so on.
|
||||
*
|
||||
* This primitive does not provide a means to identify the winning participants. If identification
|
||||
* is desired, it must be done via the yielded object, or out of band.
|
||||
*
|
||||
* @param <T> the type of object yielded by race participants
|
||||
*/
|
||||
public class AsyncRace<T> {
|
||||
private final Deque<T> finishers = new LinkedList<>();
|
||||
private final Deque<CompletableFuture<? super T>> queue = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Include a participant in this race
|
||||
*
|
||||
* @param future the participant to add
|
||||
*/
|
||||
public AsyncRace<T> include(CompletableFuture<? extends T> future) {
|
||||
future.thenAccept(t -> {
|
||||
synchronized (this) {
|
||||
if (queue.isEmpty()) {
|
||||
finishers.offer(t);
|
||||
}
|
||||
else {
|
||||
queue.poll().complete(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a future that completes with the result of the first (since last called) finishing
|
||||
* participant
|
||||
*
|
||||
* @return the next "any of" future
|
||||
*/
|
||||
public synchronized CompletableFuture<T> next() {
|
||||
if (finishers.isEmpty()) {
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
queue.offer(future);
|
||||
return future;
|
||||
}
|
||||
return CompletableFuture.completedFuture(finishers.poll());
|
||||
}
|
||||
}
|
|
@ -34,57 +34,31 @@ import java.util.function.Supplier;
|
|||
* {@link ScheduledThreadPoolExecutor}.
|
||||
*
|
||||
* <p>
|
||||
* A delay is achieved using {@link #mark()}, then {@link #after(long)}. For example, within a
|
||||
* {@link AsyncUtils#sequence(TypeSpec)}:
|
||||
* A delay is achieved using {@link #mark()}, then {@link Mark#after(long)}.
|
||||
*
|
||||
* <pre>
|
||||
* timer.mark().after(1000).handle(seq::next);
|
||||
* future.thenCompose(__ -> timer.mark().after(1000))
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* {@link #mark()} marks the current system time; all subsequent calls to {@link #after(long)}
|
||||
* schedule futures relative to this mark. Using {@link #after(long)} before {@link #mark()} gives
|
||||
* undefined behavior. Scheduling a timed sequence of actions is best accomplished using times
|
||||
* relative to a single mark. For example:
|
||||
* {@link #mark()} marks the current system time; all calls to the mark's {@link Mark#after(long)}
|
||||
* schedule futures relative to this mark. Scheduling a timed sequence of actions is best
|
||||
* accomplished using times relative to a single mark. For example:
|
||||
*
|
||||
* <pre>
|
||||
* sequence(TypeSpec.VOID).then((seq) -> {
|
||||
* timer.mark().after(1000).handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* doTaskAtOneSecond().handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* timer.after(2000).handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* doTaskAtTwoSeconds().handle(seq::next);
|
||||
* }).asCompletableFuture();
|
||||
* Mark mark = timer.mark();
|
||||
* mark.after(1000).thenCompose(__ -> {
|
||||
* doTaskAtOneSecond();
|
||||
* return mark.after(2000);
|
||||
* }).thenAccept(__ -> {
|
||||
* doTaskAtTwoSeconds();
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* This provides slightly more precise scheduling than delaying for a fixed period between tasks.
|
||||
* Consider a second example:
|
||||
*
|
||||
* <pre>
|
||||
* sequence(TypeSpec.VOID).then((seq) -> {
|
||||
* timer.mark().after(1000).handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* doTaskAtOneSecond().handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* timer.mark().after(1000).handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* doTaskAtTwoSeconds().handle(seq::next);
|
||||
* }).asCompletableFuture();
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* In the first example, {@code doTaskAtTwoSeconds} executes at 2000ms from the mark + some
|
||||
* scheduling overhead. In the second example, {@code doTaskAtTwoSeconds} executes at 1000ms + some
|
||||
* scheduling overhead + the time to execute {@code doTaskAtOneSecond} + 1000ms + some more
|
||||
* scheduling overhead. Using the second pattern repeatedly can lead to increased inaccuracies as
|
||||
* overhead accumulates over time. This may be an issue for some applications. Using the first
|
||||
* pattern, there is no accumulation. The actual scheduled time will always be the specified time
|
||||
* from the mark + some scheduling overhead. The scheduling overhead is generally bounded to a small
|
||||
* constant and depends on the accuracy of the host OS and JVM.
|
||||
*
|
||||
* <p>
|
||||
* Like {@link Timer}, each {@link AsyncTimer} is backed by a single thread which uses
|
||||
* {@link Object#wait()} to implement its timing. Thus, this is not suitable for real-time
|
||||
|
@ -108,8 +82,9 @@ public class AsyncTimer {
|
|||
/**
|
||||
* Schedule a task to run when the given number of milliseconds has passed since this mark
|
||||
*
|
||||
* <p>
|
||||
* The method returns immediately, giving a future result. The future completes "soon after"
|
||||
* the requested interval, since the last mark, passes. There is some minimal overhead, but
|
||||
* the requested interval since the last mark passes. There is some minimal overhead, but
|
||||
* the scheduler endeavors to complete the future as close to the given time as possible.
|
||||
* The actual scheduled time will not precede the requested time.
|
||||
*
|
||||
|
|
|
@ -16,396 +16,15 @@
|
|||
package ghidra.async;
|
||||
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import ghidra.async.loop.*;
|
||||
import ghidra.async.seq.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* A wrapper for Java's {@link CompletableFuture} that provides additional fluency
|
||||
*
|
||||
* <p>
|
||||
* The {@link CompletableFuture} class is very useful for organizing asynchronous tasks into tidy
|
||||
* sequences. See {@link CompletableFuture#thenCompose(Function)} for more information, though
|
||||
* better examples are available in other online resources.
|
||||
*
|
||||
* <p>
|
||||
* These utilities seek to ease some operations, e.g., loops, and provide some control of execution
|
||||
* in sequences not otherwise given by Java's futures. While some verboseness is eliminated, these
|
||||
* utilities have tradeoffs. They interoperate, permitting use of each according to circumstances
|
||||
* and taste.
|
||||
*
|
||||
* <p>
|
||||
* Additional conveniences allow Java's non-blocking IO to interoperate more closely with futures.
|
||||
* See {@link AsynchronousByteChannel}. The "nio" API predates {@link CompletableFuture}, so they
|
||||
* cannot readily interact. They use {@link CompletionHandler}s instead of futures. This class
|
||||
* provides API wrappers that return {@link CompletableFuture}s.
|
||||
*
|
||||
* <p>
|
||||
* An asynchronous method is one that returns control immediately, and may promise to provide a
|
||||
* completed result at some future time. In the case of computation, the method may launch a thread
|
||||
* or queue the computation on an executor. In the case of non-blocking IO, the method may queue a
|
||||
* handler on an asynchronous channel. The method is said to "promise" a completed result. It
|
||||
* instantiates the promise and arranges its fulfillment. It can then return control immediately by
|
||||
* returning the corresponding "future" object. When the promise is fulfilled, the result is stored
|
||||
* in the future object, optionally invoking a callback.
|
||||
*
|
||||
* <p>
|
||||
* Java implements this pattern in {@link CompletableFuture}. Java does not provide a separate class
|
||||
* for the promise. The asynchronous method instantiates the completable future, arranging for its
|
||||
* completion, and immediately returns it to the caller. Anything with a referece to the future may
|
||||
* complete it. The "promise" and "future" objects are not distinct. Note that
|
||||
* {@link AsynchronousByteChannel} does provide methods that return {@link Future}, but the
|
||||
* underlying implementation is not a {@link CompletableFuture}, so it cannot accept callbacks.
|
||||
*
|
||||
* <p>
|
||||
* There are generally two ways the caller can consume the value. The simplest is to wait, by
|
||||
* blocking, on the result. This defeats the purpose of asynchronous processing. For example:
|
||||
*
|
||||
* <pre>
|
||||
* System.out.println(processAsync(input1).get());
|
||||
* System.out.println(processAsync(input2).get());
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* This block is not any more efficient than the following sequential program:
|
||||
*
|
||||
* <pre>
|
||||
* System.out.println(processSync(input1));
|
||||
* System.out.println(processSync(input2));
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* In fact, it may be worse from the overhead of multi-threading, queueing, scheduling, etc. To take
|
||||
* advantage of multi-threaded executors, processing should be performed in parallel when possible.
|
||||
* For example:
|
||||
*
|
||||
* <pre>
|
||||
* CompletableFuture<Long> future1 = processAsync(input1);
|
||||
* CompletableFuture<Long> future2 = processAsync(input2);
|
||||
* System.out.println(future1.get());
|
||||
* System.out.println(future2.get());
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Now, both inputs can be processed simultaneously. The other way to consume the value is via
|
||||
* callback. This elegant option may seem complex. It is especially effective when the calling
|
||||
* thread cannot or should not block. For example:
|
||||
*
|
||||
* <pre>
|
||||
* processAsync(input1).thenAccept((result1) -> {
|
||||
* System.out.println(result1);
|
||||
* });
|
||||
* processAsync(input2).thenAccept((result2) -> {
|
||||
* System.out.println(result2);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Now, both inputs can be processed simultaneously, and each result is printed the moment it
|
||||
* becomes available. Another notable difference: If processing of input2 finishes first, it can be
|
||||
* printed without waiting on input1. Furthermore, the calling thread is not blocked. Long chains of
|
||||
* asynchronous callbacks, however, an become difficult to manage.
|
||||
*
|
||||
* <p>
|
||||
* Suppose a program must fetch a list of integers from a storage system, then process each entry by
|
||||
* submitting it to a remote service, and finally aggregate the results with a given initial value.
|
||||
*
|
||||
* <p>
|
||||
* The traditional implementation might look like this:
|
||||
*
|
||||
* <pre>
|
||||
* public int doWork(int start) {
|
||||
* Storage store = connectStorage(ADDR1);
|
||||
* Service serve = connectService(ADDR2);
|
||||
* List<Integer> list = store.fetchList();
|
||||
* int sum = start;
|
||||
* for (int entry : list) {
|
||||
* sum += serve.process(entry);
|
||||
* }
|
||||
* store.close();
|
||||
* serve.close();
|
||||
* return sum;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Now, suppose the connect methods, the fetch method, and the process method all perform their work
|
||||
* asynchronously, providing results via futures. The implementation ought to provide for parallel
|
||||
* execution. The chain of asynchronous tasks essentially becomes a composite task, itself
|
||||
* implementing the future pattern. One scheme is to create a class extending
|
||||
* {@link CompletableFuture} and containing all callback methods in the chain:
|
||||
*
|
||||
* <pre>
|
||||
* class FetchAndProcess extends CompletableFuture<Integer> {
|
||||
* Storage storage;
|
||||
* Service service;
|
||||
* AtomicInteger sum = new AtomicInteger();
|
||||
*
|
||||
* FetchAndProcess(int start) {
|
||||
* sum = start;
|
||||
* connectStorage(ADDR1).handle(this::storageConnected);
|
||||
* }
|
||||
*
|
||||
* Void storageConnected(Storage s, Throwable exc) {
|
||||
* if (exc != null) {
|
||||
* completeExceptionally(exc);
|
||||
* }
|
||||
* else {
|
||||
* storage = s;
|
||||
* connectService(ADDR2).handle(this::serviceConnected);
|
||||
* }
|
||||
* return null;
|
||||
* }
|
||||
*
|
||||
* Void serviceConnected(Service s, Throwable exc) {
|
||||
* if (exc != null) {
|
||||
* completeExceptionally(exc);
|
||||
* }
|
||||
* else {
|
||||
* service = s;
|
||||
* storage.fetchList().handle(this::fetchedList);
|
||||
* }
|
||||
* return null;
|
||||
* }
|
||||
*
|
||||
* Void fetchedList(List<Integer> list, Throwable exc) {
|
||||
* if (exc != null) {
|
||||
* completeExceptionally(exc);
|
||||
* }
|
||||
* else {
|
||||
* List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
* for (int entry : list) {
|
||||
* futures.add(service.process(entry).thenAccept((result) -> {
|
||||
* sum.addAndGet(result);
|
||||
* }));
|
||||
* }
|
||||
* CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]))
|
||||
* .handle(this::processedList);
|
||||
* }
|
||||
* return null;
|
||||
* }
|
||||
*
|
||||
* Void processedList(Void v, Throwable exc) {
|
||||
* if (exc != null) {
|
||||
* completeExceptionally(exc);
|
||||
* }
|
||||
* else {
|
||||
* complete(sum.get());
|
||||
* }
|
||||
* return null;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* public CompletableFuture<Integer> doWork(int start) {
|
||||
* return new FetchAndProcess(start);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The asynchronous method then just instantiates the class and returns it. The final callback in
|
||||
* the chain must call {@link CompletableFuture#complete(Object)}. The main deficit to this method
|
||||
* is that the operational steps are buried between method declarations and error checks. Also,
|
||||
* because the method must return {@link Void}, not just {@code void}, all the methods must return
|
||||
* {@code null}. Furthermore, errors that occur in the callback methods do not get communicated to
|
||||
* the caller of {@code doWork}. Additional try-cache blocks would be required.
|
||||
*
|
||||
* <p>
|
||||
* Lambda functions could be substituted for method references avoiding the class declaration, but
|
||||
* that would result in nesting several levels deep -- at least one level per callback in the chain.
|
||||
* To avoid nesting, Java provides {@link CompletableFuture#thenCompose(Function)}, which provides a
|
||||
* fairly elegant implementation:
|
||||
*
|
||||
* <pre>
|
||||
* public CompletableFuture<Integer> doWork(int start) {
|
||||
* AtomicReference<Storage> store = new AtomicReference<>();
|
||||
* AtomicReference<Service> serve = new AtomicReference<>();
|
||||
* AtomicInteger sum = new AtomicInteger(start);
|
||||
* return connectStorage(ADDR1).thenCompose((s) -> {
|
||||
* store.set(s);
|
||||
* return connectService(ADDR2);
|
||||
* }).thenCompose((s) -> {
|
||||
* serve.set(s);
|
||||
* return store.get().fetchList();
|
||||
* }).thenCompose((list) -> {
|
||||
* List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
* for (int entry : list) {
|
||||
* futures.add(serve.get().process(entry).thenAccept((result) -> {
|
||||
* sum.addAndGet(result);
|
||||
* }));
|
||||
* }
|
||||
* return CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]));
|
||||
* }).thenApply((v) -> {
|
||||
* store.get().close();
|
||||
* serve.get().close();
|
||||
* return sum.get();
|
||||
* });
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* This does have a couple notable deficits, though. First, the {@code return} keyword is almost
|
||||
* abused. The callbacks are defined inline as if they were simply actions within the method. In
|
||||
* this context, {@code return} can become misleading, because that is not the point where the
|
||||
* method terminates with a result, but rather the point where the action provides the next
|
||||
* subordinate asynchronous task in the chain. Second, not all the actions are presented at the same
|
||||
* nesting level, even though they reside in the same sequence. Because of this, it's not
|
||||
* immediately obvious that, e.g., {@code connectStorage} precedes {@code connectService}, or
|
||||
* perhaps a more subtle example, that {@code process} precedes {@code sumAndGet}.
|
||||
*
|
||||
* <p>
|
||||
* With the utilities, we can write this sequence a little differently. The goal is to mimic
|
||||
* sequential programming not just in its appearance, but also in its control structure:
|
||||
*
|
||||
* <pre>
|
||||
* public CompletableFuture<Integer> doWork(int start) {
|
||||
* AtomicReference<Storage> store = new AtomicReference<>();
|
||||
* AtomicReference<Service> serve = new AtomicReference<>();
|
||||
* AtomicInteger sum = new AtomicInteger(start);
|
||||
* return sequence(TypeSpec.INT).then((seq) -> {
|
||||
* connectStorage(ADDR1).handle(seq::next);
|
||||
* }, store).then((seq) -> {
|
||||
* connectService(ADDR2).handle(seq::next);
|
||||
* }, serve).then((seq) -> {
|
||||
* store.get().fetchList().handle(seq::next);
|
||||
* }, TypeSpec.obj((List<Integer>) null)).then((list, seq) -> {
|
||||
* AsyncFence fence = new AsyncFence();
|
||||
* for (int entry : list) {
|
||||
* fence.include(sequence(TypeSpec.VOID).then((seq2) -> {
|
||||
* serve.get().process(entry).handle(seq2::next);
|
||||
* }, TypeSpec.INT).then((result, seq2) -> {
|
||||
* sum.addAndGet(result);
|
||||
* seq2.exit();
|
||||
* }).asCompletableFuture());
|
||||
* }
|
||||
* fence.ready().handle(seq::next);
|
||||
* }).then((seq) -> {
|
||||
* store.get().close();
|
||||
* serve.get().close();
|
||||
* seq.exit(sum.get());
|
||||
* }).asCompletableFuture();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The implementation is slightly longer because the body of the for-loop is also implemented as a
|
||||
* two-step sequence instead of using {@link CompletableFuture#thenAccept(Consumer)}. First, notice
|
||||
* that the sequence starts with a call to {@link #sequence(TypeSpec)}. This essentially declares an
|
||||
* inline asynchronous method whose result will have the given type. This mimics a standard method
|
||||
* declaration where the type is given first; whereas
|
||||
* {@link CompletableFuture#thenCompose(Function)} implicitly takes the return type of the final
|
||||
* callback.
|
||||
*
|
||||
* <p>
|
||||
* Second, notice that the callback is given a reference to the sequence. Technically, this is just
|
||||
* a contextual view of the sequence's handlers. It implements two methods which may be passed to
|
||||
* {@link CompletableFuture#handle(BiFunction)}. This constitutes a major difference from the
|
||||
* composition pattern. Instead of returning a {@link CompletableFuture} to wait on, the action must
|
||||
* explicitly call {@code handle(seq::next)}. This permits the execution of additional code after
|
||||
* calling the asynchronous method in parallel. While this provides more flexibility, it also
|
||||
* provies more opportunities for mistakes. Usually, passing or calling the handler is the last line
|
||||
* in a callback action. See {@link AsyncSequenceHandlerForRunner#next(Void, Throwable)} and
|
||||
* {@link AsyncSequenceHandlerForProducer#next(Object, Throwable)}.
|
||||
*
|
||||
* <p>
|
||||
* Third, notice that the call to {@code connectStorage} is now at the same nesting level as
|
||||
* {@code connectService}. This keeps the operative parts of each action immediately visible as they
|
||||
* are left justified, have the same indentation, and are not preceded by {@code return}. However,
|
||||
* it is easy to forget {@link CompletableFuture#handle(BiFunction)}.
|
||||
*
|
||||
* <p>
|
||||
* Fourth, notice that each action is always separated by a call to
|
||||
* {@link AsyncSequenceWithoutTemp#then(AsyncSequenceActionRuns)} or one of its variants in
|
||||
* {@link AsyncSequenceWithTemp}. This generally means the lines between actions can be ignored. In
|
||||
* the composition pattern, the reader would need to see that the final action is given by
|
||||
* {@link CompletableFuture#thenApply(Function)} in order to properly comprehend the return
|
||||
* statement properly. It is not another asynchronous method as in the previous actions. Rather it
|
||||
* is the final result. The line between actions in the sequence pattern cannot be ignored when a
|
||||
* value is passed from one action directly to the following action. The call to {@code then}
|
||||
* includes the type of the passed value. Worse yet, this sometimes requires casting {@code null} to
|
||||
* an arbitrary type. In the example, a list is retrieved in the third action and processed in the
|
||||
* fourth. To specify the type, {@code null} is cast to
|
||||
* {@link List}{@code <}{@link Integer}{@code >} and passed to {@link TypeSpec#obj(Object)}. The
|
||||
* list retrieved by {@code fetchList} is received in the following action's first parameter
|
||||
* ({@code list}) of its lambda function.
|
||||
*
|
||||
* <p>
|
||||
* Fifth, notice the use of {@link AsyncFence}. This convenience class does essentially the same
|
||||
* thing as the composition and class patterns did, but provides more meaningful method names and a
|
||||
* more succinct syntax.
|
||||
*
|
||||
* <p>
|
||||
* Finally, notice the call to to {@link AsyncSequenceHandlerForRunner#exit(Object)} passing the
|
||||
* final result of the sequence. Requiring this is a bit of a nuisance, but it makes clear what the
|
||||
* result of the sequence is. Furthermore, {@code exit} can be called by any action, not just the
|
||||
* final one. In the composition pattern, execution cannot be truncated except by error handling. In
|
||||
* the sequence pattern, any action can terminate the sequence and "return" the result. Every action
|
||||
* must either call or pass one of these handlers or the sequence will abruptly halt. Also, some
|
||||
* action, usually the final one, must pass or invoke the {@code exit} handler. If the final action
|
||||
* uses {@code next}, the sequence will "return" {@code null}. In summary, {@code next} passes a
|
||||
* value to the following action while {@code exit} passes a value as the sequence result, skipping
|
||||
* the remaining actions. The result of composed sequence of actions is communicated via its own
|
||||
* completable future. This future is obtained via {@link AsyncSequenceWithoutTemp#finish()}. Note
|
||||
* that a sequence whose final action produces a temporary value does not yield a completable
|
||||
* future.
|
||||
*
|
||||
* <p>
|
||||
* Java's built-in mechanisms provide for error handling. Usually invoking
|
||||
* {@link CompletableFuture#exceptionally(Function)} on the result of
|
||||
* {@link AsyncSequenceWithoutTemp#finish()} is sufficient. To illustrate, the two connections from
|
||||
* the example are assuredly closed by appending a call to {@code exceptionally}:
|
||||
*
|
||||
* <pre>
|
||||
* return sequence(TypeSpec.INT).then((seq) -> {
|
||||
* // ...
|
||||
* }).asCompletableFuture().exceptionally((exc) -> {
|
||||
* if (store.get() != null) {
|
||||
* store.get().close();
|
||||
* }
|
||||
* if (serve.get() != null) {
|
||||
* serve.get().close();
|
||||
* }
|
||||
* return ExceptionUtils.rethrow(exc);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If errors must be handled in a more granular fashion, consider invoking
|
||||
* {@link CompletableFuture#exceptionally(Function)} on the appropriate asynchronous task before
|
||||
* calling {@link CompletableFuture#handle(BiFunction)}. For example:
|
||||
*
|
||||
* <pre>
|
||||
* store.get().fetchList().exceptionally((exc) -> {
|
||||
* if (exc instanceof ListNotFoundException) {
|
||||
* return DEFAULT_LIST;
|
||||
* }
|
||||
* return ExceptionUtils.rethrow(exc);
|
||||
* }).handle(seq::next);
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Alternatively:
|
||||
*
|
||||
* <pre>
|
||||
* store.get().fetchList().handle(seq::next).exceptionally((exc) -> {
|
||||
* if (exc instanceof ListNotFoundException) {
|
||||
* seq.next(DEFAULT_LIST);
|
||||
* }
|
||||
* else {
|
||||
* seq.exit(exc);
|
||||
* }
|
||||
* return null;
|
||||
* });
|
||||
* </pre>
|
||||
* Some conveniences when dealing with Java's {@link CompletableFuture}s.
|
||||
*/
|
||||
public interface AsyncUtils<T> {
|
||||
public interface AsyncUtils {
|
||||
Cleaner CLEANER = Cleaner.create();
|
||||
|
||||
ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool();
|
||||
|
@ -418,421 +37,6 @@ public interface AsyncUtils<T> {
|
|||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
public static <T> CompletableFuture<T> nil(Class<T> cls) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeatedly launch an asynchronous task and process the result
|
||||
*
|
||||
* <p>
|
||||
* This loosely corresponds to a while loop. The invocation consists of two actions: One to
|
||||
* launch a subordinate task, likely producing a result, and the second to consume the result
|
||||
* and repeat the loop. Note that the loop may be repeated in parallel to the processing of the
|
||||
* result by calling {@link AsyncLoopHandlerForSecond#repeat()} early in the consumer action.
|
||||
* Either action may explicitly exit the loop, optionally providing a result. Ordinarily, the
|
||||
* loop repeats indefinitely.
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* loop(TypeSpec.VOID, (loop) -> {
|
||||
* receiveData().handle(loop::consume);
|
||||
* }, TypeSpec.BYTE_ARRAY, (data, loop) -> {
|
||||
* loop.repeat();
|
||||
* processData(data);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param loopType the type of the result of the whole loop. This is usually
|
||||
* {@link TypeSpec#VOID}.
|
||||
* @param producer an action invoking a subordinate asynchronous task, usually producing some
|
||||
* result.
|
||||
* @param iterateType the type of result produced by the subordinate asynchronous task
|
||||
* @param consumer an action consuming the result of the task and explicitly repeating or
|
||||
* exiting the loop
|
||||
* @return a future which completes upon explicit loop termination
|
||||
*/
|
||||
public static <T, U> CompletableFuture<T> loop(TypeSpec<T> loopType,
|
||||
AsyncLoopFirstActionProduces<T, U> producer, TypeSpec<U> iterateType,
|
||||
AsyncLoopSecondActionConsumes<T, ? super U> consumer) {
|
||||
AsyncLoop<T, U> loop = new AsyncLoop<>(producer, iterateType, consumer);
|
||||
loop.begin();
|
||||
return loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeatedly launch an asynchronous task
|
||||
*
|
||||
* This loosely corresponds to a while loop. This invocation consists of a single action: To
|
||||
* launch a subordinate task, producing no result, or to exit the loop. The subordinate task
|
||||
* should repeat the loop upon completion. If the loop does not require a subordinate
|
||||
* asynchronous task, then please use an actual Java {@code while} loop. If the subordinate task
|
||||
* does produce a result, it must be ignored using
|
||||
* {@link AsyncLoopHandlerForSecond#repeatIgnore(Object, Throwable)}.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* loop(TypeSpec.VOID, (loop) -> {
|
||||
* someTask().handle(loop::repeat);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param loopType the type of the result of the whole loop. This is usually
|
||||
* {@link TypeSpec.VOID}.
|
||||
* @param action an action launching a subordinate task, producing no, i.e., a {@link Void}
|
||||
* result, upon whose completion the loop repeats.
|
||||
* @return a future which completes upon explicit loop termination
|
||||
*/
|
||||
public static <T> CompletableFuture<T> loop(TypeSpec<T> loopType,
|
||||
AsyncLoopOnlyActionRuns<T> action) {
|
||||
return loop(loopType, (handler) -> {
|
||||
handler.consume(null, null);
|
||||
}, TypeSpec.VOID, (v, handler) -> {
|
||||
action.accept(handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a task for each element given by an iterator and process the result
|
||||
*
|
||||
* This loosely corresponds to a for loop. This invocation consists of two actions: One to
|
||||
* consume the element and launch a subordinate task, likely producing a result, and the second
|
||||
* to consume the result and repeat the loop. Note that the loop may be repeated in parallel to
|
||||
* the processing of the result by calling {@link AsyncLoopHandlerForSecond#repeat()} early in
|
||||
* the consumer action. Either action may explicitly exit the loop, optionally providing a
|
||||
* result. Ordinarily, the loop executes until the iterator is exhausted, completing with
|
||||
* {@code null}.
|
||||
*
|
||||
* This operates similarly to
|
||||
* {@link #loop(TypeSpec, AsyncLoopFirstActionProduces, TypeSpec, AsyncLoopSecondActionConsumes)}
|
||||
* except that it is controlled by an iterator.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
|
||||
* sendItem().handle(loop::consume);
|
||||
* }, TypeSpec.STRING, (message, loop) -> {
|
||||
* loop.repeat();
|
||||
* logResult(message);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param loopType the type of the result of the whole loop. This is usually
|
||||
* {@link TypeSpec#VOID}.
|
||||
* @param it the iterator controlling the loop and providing elements
|
||||
* @param producer an action consuming each element and invoking a subordinate asynchronous
|
||||
* task, usually producing some result.
|
||||
* @param iterateType the type of result produced by the subordinate asynchronous task.
|
||||
* @param consumer and action consuming the result of the task and explicitly repeating or
|
||||
* exiting the loop.
|
||||
* @return a future which completes upon loop termination
|
||||
*/
|
||||
public static <T, E, U> CompletableFuture<T> each(TypeSpec<T> loopType, Iterator<E> it,
|
||||
AsyncLoopFirstActionConsumesAndProduces<T, E, U> producer, TypeSpec<U> iterateType,
|
||||
AsyncLoopSecondActionConsumes<T, U> consumer) {
|
||||
return loop(loopType, (handler) -> {
|
||||
if (it.hasNext()) {
|
||||
E elem;
|
||||
try {
|
||||
elem = it.next();
|
||||
}
|
||||
catch (Throwable exc) {
|
||||
handler.exit(null, exc);
|
||||
return;
|
||||
}
|
||||
producer.accept(elem, handler);
|
||||
}
|
||||
else {
|
||||
handler.exit(null, null);
|
||||
}
|
||||
}, iterateType, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a task for each element given by an iterator
|
||||
*
|
||||
* This loosely corresponds to a for loop. This invocation consists of a single action: To
|
||||
* consume the element and launch a subordinate task, producing no result, or to exit the loop.
|
||||
* The subordinate task should repeat the loop upon completion. If the loop does not require a
|
||||
* subordinate asynchronous task, then please use an actual Java {@code for} loop. If the
|
||||
* subordinate task does produce a result, it must be ignored using
|
||||
* {@link AsyncLoopHandlerForSecond#repeatIgnore(Object, Throwable)}.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
|
||||
* sendItem().handle(loop::repeatIgnore);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param loopType the type of the result of the whole loop. This is usually
|
||||
* {@link TypeSpec#VOID}.
|
||||
* @param it the iterator controlling the loop and providing elements
|
||||
* @param action an action consuming each element and launching a subordinate asynchronous task,
|
||||
* producing no result, upon whose completion the loop repeats.
|
||||
* @return a future which completes upon loop termination
|
||||
*/
|
||||
public static <T, E> CompletableFuture<T> each(TypeSpec<T> loopType, Iterator<E> it,
|
||||
AsyncLoopSecondActionConsumes<T, E> action) {
|
||||
return each(loopType, it, (e, loop) -> {
|
||||
loop.consume(e, null);
|
||||
}, TypeSpec.obj((E) null), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin executing a sequence of actions
|
||||
*
|
||||
* This is a wrapper for Java's {@link CompletableFuture#thenCompose(Function)}. It aims to
|
||||
* provide a little more flexibility. See the class documentation for a more thorough
|
||||
* explanation with examples.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* public CompletableFuture<Void> exampleSeq() {
|
||||
* return sequence(TypeSpec.VOID).then((seq) -> {
|
||||
* fetchValue().handle(seq::next);
|
||||
* }, TypeSpec.INT).then((val, seq) -> {
|
||||
* convertValue(val + 10).handle(seq::next);
|
||||
* }, TypeSpec.STRING).then((str, seq) -> {
|
||||
* System.out.println(str);
|
||||
* seq.exit();
|
||||
* }).asCompletableFuture();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Note that the sequence begins executing on the calling thread.
|
||||
*
|
||||
* @param type the type "returned" by the sequence
|
||||
* @return an empty sequence ready to execute actions on the calling thread.
|
||||
*/
|
||||
public static <R> AsyncSequenceWithoutTemp<R> sequence(TypeSpec<R> type) {
|
||||
return sequence(new CompletableFuture<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin executing a sequence of actions to complete a given future
|
||||
*
|
||||
* When using this variant, take care to call {@link AsyncSequenceWithoutTemp#finish()} or use
|
||||
* {@link AsyncHandlerCanExit#exit(Object, Throwable)} in the final action. Otherwise, the
|
||||
* sequence will not notify dependents of completion.
|
||||
*
|
||||
* @see #sequence(TypeSpec)
|
||||
* @param on the future to complete
|
||||
* @return an empty sequence ready to execute actions on the calling thread.
|
||||
*/
|
||||
public static <R> AsyncSequenceWithoutTemp<R> sequence(CompletableFuture<R> on) {
|
||||
return new AsyncSequenceWithoutTemp<>(on, AsyncUtils.nil());
|
||||
}
|
||||
|
||||
/**
|
||||
* An adapter for methods accepting {@link CompletionHandler}s
|
||||
*
|
||||
* This class implements {@link CompletionHandler} with a wrapped {@link CompletableFuture}. It
|
||||
* can be given to a method expecting a {@link CompletionHandler}, and the wrapped future can be
|
||||
* given as the result for an asynchronous task. This allows methods expecting a
|
||||
* {@link CompletionHandler} to participate in action callback chains.
|
||||
*
|
||||
* @param <T> the type "returned" by the asynchronous task
|
||||
* @param <A> the type of attachment expected by the method. Usually {@link Object} is
|
||||
* sufficient, because the attachment is not passed to the wrapped future.
|
||||
*/
|
||||
static class FutureCompletionHandler<T, A> implements CompletionHandler<T, A> {
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
|
||||
@Override
|
||||
public void completed(T result, A attachment) {
|
||||
future.complete(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, A attachment) {
|
||||
future.completeExceptionally(exc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface describing methods that accept an attachment and a {@link CompletionHandler}
|
||||
*
|
||||
* @param <T> the type "returned" to the {@link CompletionHandler}
|
||||
*/
|
||||
interface TakesCompletionHandlerArity0<T> {
|
||||
<A> void launch(A attachment, CompletionHandler<T, ? super A> handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link TakesCompletionHandlerArity0} but with one additional parameter
|
||||
*
|
||||
* @param <T> the type "returned" to the {@link CompletionHandler}
|
||||
* @param <P0> the type of the first parameter
|
||||
*/
|
||||
interface TakesCompletionHandlerArity1<T, P0> {
|
||||
<A> void launch(P0 arg0, A attachment, CompletionHandler<T, ? super A> handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link TakesCompletionHandlerArity0} but with two additional parameters
|
||||
*
|
||||
* @param <T> the type "returned" to the {@link CompletionHandler}
|
||||
* @param <P0> the type of the first parameter
|
||||
* @param <P1> the type of the second parameter
|
||||
*/
|
||||
interface TakesCompletionHandlerArity2<T, P0, P1> {
|
||||
<A> void launch(P0 arg0, P1 arg1, A attachment, CompletionHandler<T, ? super A> handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link TakesCompletionHandlerArity0} but with three additional parameters
|
||||
*
|
||||
* @param <T> the type "returned" to the {@link CompletionHandler}
|
||||
* @param <P0> the type of the first parameter
|
||||
* @param <P1> the type of the second parameter
|
||||
* @param <P2> the type of the third parameter
|
||||
*/
|
||||
interface TakesCompletionHandlerArity3<T, P0, P1, P2> {
|
||||
<A> void launch(P0 arg0, P1 arg1, P2 arg2, A attachment,
|
||||
CompletionHandler<T, ? super A> handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link TakesCompletionHandlerArity0} but with four additional parameters
|
||||
*
|
||||
* @param <T> the type "returned" to the {@link CompletionHandler}
|
||||
* @param <P0> the type of the first parameter
|
||||
* @param <P1> the type of the second parameter
|
||||
* @param <P2> the type of the third parameter
|
||||
* @param <P3> the type of the fourth parameter
|
||||
*/
|
||||
interface TakesCompletionHandlerArity4<T, P0, P1, P2, P3> {
|
||||
<A> void launch(P0 arg0, P1 arg1, P2 arg2, P3 arg3, A attachment,
|
||||
CompletionHandler<T, ? super A> handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
|
||||
*
|
||||
* Many non-blocking IO methods' last two parameters are an attachment and a
|
||||
* {@link CompletionHandler}, e.g.,
|
||||
* {@link AsynchronousSocketChannel#read(java.nio.ByteBuffer, Object, CompletionHandler)}. This
|
||||
* method can wrap those methods, returning a {@link CompletableFuture} instead.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* completable(TypeSpec.INT, channel::read, buf).thenAccept((len) -> {
|
||||
* // Check length and process received data
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* To help Java's template resolution, the first parameter is a {@link TypeSpec}. The second is
|
||||
* a reference to the NIO method. Following that are up to four parameters to pass to the
|
||||
* wrapped method. These correspond to the arguments preceding the attachment. Mismatched types
|
||||
* are detected at compile time.
|
||||
*
|
||||
* @param type the type "returned" by the {@link CompletionHandler}
|
||||
* @param func the function launching the asynchronous task
|
||||
* @return the future to receive the completion result
|
||||
*/
|
||||
public static <T, A> CompletableFuture<T> completable(TypeSpec<T> type,
|
||||
TakesCompletionHandlerArity0<T> func) {
|
||||
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
|
||||
func.launch(null, handler);
|
||||
return handler.future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
|
||||
*
|
||||
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
|
||||
*
|
||||
* @param type the type "returned" by the {@link CompletionHandler}
|
||||
* @param func the function launching the asynchronous task
|
||||
* @return the future to receive the completion result
|
||||
*/
|
||||
public static <T, P0, A> CompletableFuture<T> completable(TypeSpec<T> type,
|
||||
TakesCompletionHandlerArity1<T, P0> func, P0 arg0) {
|
||||
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
|
||||
func.launch(arg0, null, handler);
|
||||
return handler.future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
|
||||
*
|
||||
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
|
||||
*
|
||||
* @param type the type "returned" by the {@link CompletionHandler}
|
||||
* @param func the function launching the asynchronous task
|
||||
* @return the future to receive the completion result
|
||||
*/
|
||||
public static <T, P0, P1, A> CompletableFuture<T> completable(TypeSpec<T> type,
|
||||
TakesCompletionHandlerArity2<T, P0, P1> func, P0 arg0, P1 arg1) {
|
||||
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
|
||||
func.launch(arg0, arg1, null, handler);
|
||||
return handler.future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
|
||||
*
|
||||
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
|
||||
*
|
||||
* @param type the type "returned" by the {@link CompletionHandler}
|
||||
* @param func the function launching the asynchronous task
|
||||
* @return the future to receive the completion result
|
||||
*/
|
||||
public static <T, P0, P1, P2, A> CompletableFuture<T> completable(TypeSpec<T> type,
|
||||
TakesCompletionHandlerArity3<T, P0, P1, P2> func, P0 arg0, P1 arg1, P2 arg2) {
|
||||
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
|
||||
func.launch(arg0, arg1, arg2, null, handler);
|
||||
return handler.future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
|
||||
*
|
||||
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
|
||||
*
|
||||
* @param type the type "returned" by the {@link CompletionHandler}
|
||||
* @param func the function launching the asynchronous task
|
||||
* @return the future to receive the completion result
|
||||
*/
|
||||
public static <T, P0, P1, P2, P3, A> CompletableFuture<T> completable(TypeSpec<T> type,
|
||||
TakesCompletionHandlerArity4<T, P0, P1, P2, P3> func, P0 arg0, P1 arg1, P2 arg2,
|
||||
P3 arg3) {
|
||||
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
|
||||
func.launch(arg0, arg1, arg2, arg3, null, handler);
|
||||
return handler.future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a {@link CompletableFuture} as a {@link CompletionHandler}-style asynchronous task
|
||||
*
|
||||
* This is used only in diagnostic classes to implement {@link CompletionHandler}-style
|
||||
* asynchronous tasks. It is the opposite adapter to
|
||||
* {@link #completable(TypeSpec, TakesCompletionHandlerArity0)} and its overloaded variants.
|
||||
*
|
||||
* @param future the future to wrap
|
||||
* @param handler a handler to receive the callback on future completion
|
||||
*/
|
||||
public static <T, A> void handle(CompletableFuture<T> future, A attachment,
|
||||
CompletionHandler<T, ? super A> handler) {
|
||||
future.handle((result, exc) -> {
|
||||
if (exc != null) {
|
||||
handler.failed(exc, attachment);
|
||||
}
|
||||
else {
|
||||
handler.completed(result, attachment);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public interface TemperamentalRunnable {
|
||||
public void run() throws Throwable;
|
||||
}
|
||||
|
@ -841,23 +45,6 @@ public interface AsyncUtils<T> {
|
|||
public T get() throws Throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience for protecting engines from errors in user callbacks
|
||||
*
|
||||
* If not used, then when multiple listeners are present, those following a listener whose
|
||||
* callback generates an error may never actually be notified.
|
||||
*
|
||||
* @param cb the invocation of the user callback
|
||||
*/
|
||||
public static void defensive(TemperamentalRunnable cb) {
|
||||
try {
|
||||
cb.run();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(cb, "Error in callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap {@link CompletionException}s and {@link ExecutionException}s to get the real cause
|
||||
*
|
||||
|
|
|
@ -20,14 +20,11 @@ import java.util.concurrent.*;
|
|||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import ghidra.async.seq.AsyncSequenceActionRuns;
|
||||
import ghidra.async.seq.AsyncSequenceWithoutTemp;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link SwingUtilities#invokeLater(Runnable)} that implements
|
||||
* {@link ExecutorService}. This makes it a suitable first parameter to
|
||||
* {@link AsyncSequenceWithoutTemp#then(ExecutorService, AsyncSequenceActionRuns)} and similar.
|
||||
* {@link ExecutorService}.
|
||||
*/
|
||||
public abstract class SwingExecutorService extends AbstractExecutorService {
|
||||
public static final SwingExecutorService LATER = new SwingExecutorService() {
|
||||
|
@ -74,5 +71,4 @@ public abstract class SwingExecutorService extends AbstractExecutorService {
|
|||
public boolean awaitTermination(long timeout, TimeUnit unit) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,344 +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.async;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.async.seq.AsyncSequenceWithoutTemp;
|
||||
|
||||
/**
|
||||
* An interface for type specification in sequences
|
||||
*
|
||||
* This is just fodder for Java's generic type system. Sometimes it is not intelligent enough to
|
||||
* resolve a type parameter, especially when passing a lambda function. A workaround, albeit
|
||||
* hackish, is to add an unused argument to aid resolution. Take for example, the utility method
|
||||
* {@link AsyncUtils#sequence(TypeSpec)}. It returns a {@link AsyncSequenceWithoutTemp}{@code <R>}.
|
||||
* Were it not for the argument {@code TypeSpec<R> type}, Java could only resolve {@code <R>} by
|
||||
* assigning the sequence to a temporary variable. This would require an extra line for the
|
||||
* assignment, including the full specification of the type, e.g.:
|
||||
*
|
||||
* <pre>
|
||||
* AsyncSequenceWithoutTemp<Integer> s = sequence().then((seq) -> {
|
||||
* // Do stuff
|
||||
* });
|
||||
* return s.asCompletableFuture();
|
||||
* </pre>
|
||||
*
|
||||
* However, this makes the definition exceedingly verbose, and it exposes the implementation
|
||||
* details. While the unused argument is a nuisance, it is preferred to the above alternative. Thus,
|
||||
* the static methods in this class seek to ease obtaining an appropriate {@code TypeSpec}. Several
|
||||
* primitive and common types are pre-defined.
|
||||
*
|
||||
* This interface is not meant to be implemented by any classes or extended by other interfaces. The
|
||||
* runtime value of a {@code TypeSpec} argument is always {@link #RAW}. The arguments only serve a
|
||||
* purpose at compile time.
|
||||
*
|
||||
* TODO: Look at TypeLiteral instead....
|
||||
*
|
||||
* @param <U> the type of this specification
|
||||
*/
|
||||
public interface TypeSpec<U> {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static final TypeSpec RAW = new TypeSpec() {
|
||||
/*
|
||||
* Nothing to put here. This one instance will just be cast to satisfy the compiler. I wish
|
||||
* this didn't blow runtime cycles.
|
||||
*/
|
||||
};
|
||||
public static final TypeSpec<Object> OBJECT = auto();
|
||||
public static final TypeSpec<Boolean> BOOLEAN = auto();
|
||||
public static final TypeSpec<Byte> BYTE = auto();
|
||||
public static final TypeSpec<Character> CHAR = auto();
|
||||
public static final TypeSpec<Short> SHORT = auto();
|
||||
public static final TypeSpec<Integer> INT = auto();
|
||||
public static final TypeSpec<Long> LONG = auto();
|
||||
public static final TypeSpec<String> STRING = auto();
|
||||
public static final TypeSpec<Void> VOID = auto();
|
||||
|
||||
public static final TypeSpec<byte[]> BYTE_ARRAY = auto();
|
||||
|
||||
/**
|
||||
* Obtain the most concrete type specifier suitable in the context
|
||||
*
|
||||
* This is a sort of syntactic filler to satisfy Java's type checker while carrying useful type
|
||||
* information from action to action of an asynchronous sequence. This method is likely
|
||||
* preferred for all cases. The cases where this is not used are equivalent to the explicit use
|
||||
* of type annotations in normal synchronous programming. Either the programmer would like to
|
||||
* ensure an intermediate result indeed has the given type, or the programmer would like to
|
||||
* ascribe a more abstract type to the result.
|
||||
*
|
||||
* NOTE: For some compilers, this doesn't work in all contexts. It tends to work in Eclipse, but
|
||||
* not in Gradle when used with directly
|
||||
* {@link AsyncSequenceWithoutTemp#then(java.util.concurrent.Executor, ghidra.async.seq.AsyncSequenceActionProduces, TypeSpec)}.
|
||||
* Not sure what compiler option(s) are causing the difference.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public static <T> TypeSpec<T> auto() {
|
||||
return RAW;
|
||||
}
|
||||
|
||||
public static <T> TypeSpec<T> from(Future<T> future) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier of a given raw class type
|
||||
*
|
||||
* @param cls the type of the producer
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <U> TypeSpec<U> cls(Class<U> cls) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier of a type given by an example
|
||||
*
|
||||
* @param example the example having the desired type, often {@code null} cast to the type
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <U> TypeSpec<U> obj(U example) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for a collection of this type
|
||||
*
|
||||
* @return the collection specifier
|
||||
*/
|
||||
public default <C extends Collection<U>> TypeSpec<C> col() {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for a given collection type of this type
|
||||
*
|
||||
* @param <C> the type of collection
|
||||
* @param cls the raw type of the collection
|
||||
* @return the collection specifier
|
||||
*/
|
||||
public default <C extends Collection<U>> TypeSpec<C> col(Class<? super C> cls) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for a set of this type
|
||||
*
|
||||
* @return the collection specifier
|
||||
*/
|
||||
public default <C extends Set<U>> TypeSpec<C> set() {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for a list of this type
|
||||
*
|
||||
* @return the collection specifier
|
||||
*/
|
||||
public default <C extends List<U>> TypeSpec<C> list() {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for a list of this type
|
||||
*
|
||||
* @return the collection specifier
|
||||
*/
|
||||
/*public default <C extends List<? extends U>> TypeSpec<C> listExt() {
|
||||
return auto();
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Object a type specifier which allows extensions of this type
|
||||
*
|
||||
* @return the "extends" type specifier
|
||||
*/
|
||||
public default TypeSpec<? extends U> ext() {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for a map from the given type to this type
|
||||
*
|
||||
* @param <K> the type of key
|
||||
* @param keyType the type specifier for the keys
|
||||
* @return the map type specifier
|
||||
*/
|
||||
public default <K> TypeSpec<Map<K, U>> mappedBy(TypeSpec<K> keyType) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for a map from the given class to this type
|
||||
*
|
||||
* @param <K> the type of key
|
||||
* @param keyCls the class for the keys
|
||||
* @return the map type specifier
|
||||
*/
|
||||
public default <K> TypeSpec<Map<K, U>> mappedBy(Class<K> keyCls) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier of a map given the raw class types for keys and values
|
||||
*
|
||||
* @param keyCls the type for keys
|
||||
* @param valCls the type for values
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <K, V> TypeSpec<Map<K, V>> map(Class<K> keyCls, Class<V> valCls) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
public static <L, R> TypeSpec<Pair<L, R>> pair(TypeSpec<L> lSpec, TypeSpec<R> rSpec) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for methods of 0 arguments
|
||||
*
|
||||
* @param <R> the return type of the method
|
||||
*/
|
||||
interface FuncArity0<R> {
|
||||
R func();
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for methods of 1 argument
|
||||
*
|
||||
* @param <R> the return type of the method
|
||||
* @param <P0> the type of the first parameter
|
||||
*/
|
||||
interface FuncArity1<R, P0> {
|
||||
R func(P0 arg0);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for methods of 2 arguments
|
||||
*
|
||||
* @param <R> the return type of the method
|
||||
* @param <P0> the type of the first parameter
|
||||
* @param <P1> the type of the second parameter
|
||||
*/
|
||||
interface FuncArity2<R, P0, P1> {
|
||||
R func(P0 arg0, P1 arg1);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for methods of 3 arguments
|
||||
*
|
||||
* @param <R> the return type of the method
|
||||
* @param <P0> the type of the first parameter
|
||||
* @param <P1> the type of the second parameter
|
||||
* @param <P2> the type of the third parameter
|
||||
*/
|
||||
interface FuncArity3<R, P0, P1, P2> {
|
||||
R func(P0 arg0, P1 arg1, P2 arg2);
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for methods of 4 arguments
|
||||
*
|
||||
* @param <R> the return type of the method
|
||||
* @param <P0> the type of the first parameter
|
||||
* @param <P1> the type of the second parameter
|
||||
* @param <P2> the type of the third parameter
|
||||
* @param <P3> the type of the fourth parameter
|
||||
*/
|
||||
interface FuncArity4<R, P0, P1, P2, P3> {
|
||||
R func(P0 arg0, P1 arg1, P2 arg2, P3 arg3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for the result of an asynchronous method
|
||||
*
|
||||
* This is a shortcut for asynchronous methods whose implementations return a completable future
|
||||
* from {@link AsyncUtils#sequence(TypeSpec)}, especially when the result is a complicated type.
|
||||
* To work, the referenced method, usually the containing method, must return an implementation
|
||||
* of {@link Future}. For example:
|
||||
*
|
||||
* <pre>
|
||||
* public CompletableFuture<Map<String, Set<Integer>>> computeNamedSets() {
|
||||
* return sequence(TypeSpec.future(this::computeNamedSets)).then((seq) -> {
|
||||
* // Do computation
|
||||
* }).asCompletableFuture();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* This causes the sequence to have the correct type such that
|
||||
* {@link AsyncSequenceWithoutTemp#finish()} returns a future having type compatible with the
|
||||
* return type of the function. The referred method may take up to four parameters. Depending on
|
||||
* optimizations applied by the JVM, this shortcut costs one instantiation of a method reference
|
||||
* that is never used.
|
||||
*
|
||||
* @param func the method returning a {@link Future}
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <U> TypeSpec<U> future(FuncArity0<? extends Future<U>> func) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for the result of an asynchronous method
|
||||
*
|
||||
* @see #future(FuncArity0)
|
||||
* @param func the method returning a {@link Future}
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <U, P0> TypeSpec<U> future(FuncArity1<? extends Future<U>, P0> func) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for the result of an asynchronous method
|
||||
*
|
||||
* @see #future(FuncArity0)
|
||||
* @param func the method returning a {@link Future}
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <U, P0, P1> TypeSpec<U> future(FuncArity2<? extends Future<U>, P0, P1> func) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for the result of an asynchronous method
|
||||
*
|
||||
* @see #future(FuncArity0)
|
||||
* @param func the method returning a {@link Future}
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <U, P0, P1, P2> TypeSpec<U> future(
|
||||
FuncArity3<? extends Future<U>, P0, P1, P2> func) {
|
||||
return auto();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a type specifier for the result of an asynchronous method
|
||||
*
|
||||
* @see #future(FuncArity0)
|
||||
* @param func the method returning a {@link Future}
|
||||
* @return the specifier
|
||||
*/
|
||||
public static <U, P0, P1, P2, P3> TypeSpec<U> future(
|
||||
FuncArity4<? extends Future<U>, P0, P1, P2, P3> func) {
|
||||
return auto();
|
||||
}
|
||||
}
|
|
@ -1,121 +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.async.loop;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
|
||||
/**
|
||||
* The underlying implementation of
|
||||
* {@link AsyncUtils#loop(TypeSpec, AsyncLoopFirstActionProduces, TypeSpec, AsyncLoopSecondActionConsumes)}
|
||||
*
|
||||
* @param <R> the type of result for the whole loop, usually {@link TypeSpec#VOID}
|
||||
* @param <T> the type of result produced by the subordinate asynchronous task
|
||||
*/
|
||||
public class AsyncLoop<R, T> extends CompletableFuture<R> {
|
||||
private final AsyncLoopFirstActionProduces<R, T> producer;
|
||||
private final AsyncLoopSecondActionConsumes<R, ? super T> consumer;
|
||||
|
||||
/**
|
||||
* Execute the first action of the loop
|
||||
*/
|
||||
public void begin() {
|
||||
consumeHandler.repeat(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler given to the producer action of the loop
|
||||
*/
|
||||
private final AsyncLoopHandlerForFirst<R, T> produceHandler =
|
||||
new AsyncLoopHandlerForFirst<R, T>() {
|
||||
@Override
|
||||
public Void consume(T iterate, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
consumer.accept(iterate, consumeHandler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void exit(R result, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
complete(result);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The handler given to the consumer action of the loop
|
||||
*/
|
||||
private final AsyncLoopHandlerForSecond<R> consumeHandler = new AsyncLoopHandlerForSecond<R>() {
|
||||
@Override
|
||||
public Void repeat(Void v, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
// This is a hack to avoid stack overflows
|
||||
AsyncUtils.FRAMEWORK_EXECUTOR.submit(() -> {
|
||||
try {
|
||||
producer.accept(produceHandler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void exit(R result, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
complete(result);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a loop with the given producer and consumer
|
||||
*
|
||||
* @param producer the producer (first) action
|
||||
* @param type the type of object passed from producer to consumer
|
||||
* @param consumer the consumer (second) action
|
||||
*/
|
||||
public AsyncLoop(AsyncLoopFirstActionProduces<R, T> producer, TypeSpec<T> type,
|
||||
AsyncLoopSecondActionConsumes<R, ? super T> consumer) {
|
||||
this.producer = producer;
|
||||
this.consumer = consumer;
|
||||
}
|
||||
}
|
|
@ -1,36 +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.async.loop;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
|
||||
/**
|
||||
* The interface for the first action of an iterator-controlled loop
|
||||
*
|
||||
* @see AsyncUtils#each(ghidra.async.TypeSpec, java.util.Iterator,
|
||||
* AsyncLoopFirstActionConsumesAndProduces, ghidra.async.TypeSpec,
|
||||
* AsyncLoopSecondActionConsumes)
|
||||
*
|
||||
* @param <R> the type of result for the whole loop
|
||||
* @param <E> the type of object consumed, i.e., provided by the controlling iterator
|
||||
* @param <T> the type of object produced, i.e., provided by the subordinate asynchronous task
|
||||
*/
|
||||
public interface AsyncLoopFirstActionConsumesAndProduces<R, E, T>
|
||||
extends BiConsumer<E, AsyncLoopHandlerForFirst<R, T>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,34 +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.async.loop;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
|
||||
/**
|
||||
* The interface for the first action of a plain loop
|
||||
*
|
||||
* @see AsyncUtils#loop(ghidra.async.TypeSpec, AsyncLoopFirstActionProduces,
|
||||
* ghidra.async.TypeSpec, AsyncLoopSecondActionConsumes)
|
||||
*
|
||||
* @param <R> the type of result for the whole loop
|
||||
* @param <T> the type of result produced, i.e., provided by the subordinate asynchronous task
|
||||
*/
|
||||
public interface AsyncLoopFirstActionProduces<R, T>
|
||||
extends Consumer<AsyncLoopHandlerForFirst<R, T>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,48 +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.async.loop;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import ghidra.async.*;
|
||||
|
||||
/**
|
||||
* The handler given to the first action of a two-action loop
|
||||
*
|
||||
* @param <R> the type of result for the whole loop
|
||||
* @param <T> the type of object produced, i.e., provided by the subordinate asynchronous task
|
||||
*/
|
||||
public interface AsyncLoopHandlerForFirst<R, T> extends AsyncHandlerCanExit<R> {
|
||||
/**
|
||||
* Execute the consumer action or complete the loop exceptionally
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)}. This should rarely if ever be invoked directly
|
||||
* since it is most often used to handle future completion of a subordinate asynchronous task.
|
||||
* If it is invoked directly, consider using a single-action loop, i.e.,
|
||||
* {@link AsyncUtils#loop(TypeSpec, AsyncLoopOnlyActionRuns)} or
|
||||
* {@link AsyncUtils#each(TypeSpec, Iterator, AsyncLoopSecondActionConsumes)}.
|
||||
*
|
||||
* If the subordinate completes without exception, the consumer is executed with the result. If
|
||||
* it completes exceptionally, then the whole loop completes exceptionally and terminates.
|
||||
*
|
||||
* @param elemResult the result of completion
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return null
|
||||
*/
|
||||
public Void consume(T elemResult, Throwable exc);
|
||||
}
|
|
@ -1,107 +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.async.loop;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import ghidra.async.*;
|
||||
|
||||
/**
|
||||
* The handler given to the second or only action of a loop
|
||||
*
|
||||
* @param <R> the type of result for the whole loop
|
||||
*/
|
||||
public interface AsyncLoopHandlerForSecond<R> extends AsyncHandlerCanExit<R> {
|
||||
/**
|
||||
* Re-execute the producer action or complete the loop exceptionally
|
||||
*
|
||||
* For single-action loops, this re-executes the single action
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)}. While it can be invoked directly, consider the
|
||||
* convenience method {@link #repeat()}.
|
||||
*
|
||||
* If the subordinate completes without exception, the loop is repeated. If it completes
|
||||
* exceptionally, then the whole loop completes exceptionally and terminates.
|
||||
*
|
||||
* @param v null placeholder for {@link Void}
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return null
|
||||
*/
|
||||
public Void repeat(Void v, Throwable exc);
|
||||
|
||||
/**
|
||||
* Re-execute the producer action or exit conditionally, or complete exceptionally
|
||||
*
|
||||
* For single-action loops, this re-executes the single action
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)}. While it can be invoked directly, consider the
|
||||
* convenience method {@link #repeatWhile(boolean)}.
|
||||
*
|
||||
* If the subordinate completes without exception, its result value {@code b} is examined. If
|
||||
* equal to {@link Boolean.TRUE}, the loop is repeated; otherwise the loop exits, i.e.,
|
||||
* completes normally. If the subordinate completes exceptionally, then the whole loop completes
|
||||
* exceptionally and terminates.
|
||||
*
|
||||
* @param b the value of the predicate
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return null
|
||||
*/
|
||||
public default Void repeatWhile(Boolean b, Throwable exc) {
|
||||
return exc == null && b == Boolean.TRUE ? repeat(null, exc) : exit(null, exc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do like {@link #repeat(Void, Throwable)}, but ignore the result of a subordinate task
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)} for any type. There is no need to invoke this
|
||||
* method directly. If the subordinate asynchronous task produces a result, and that result does
|
||||
* not need to be processed before the loop repeats, this method must be used, since
|
||||
* {@link #repeat(Void, Throwable)} requires the {@link CompletableFuture} to provide a
|
||||
* {@link Void} result. If the result cannot be ignored, consider using a two-action loop, i.e.,
|
||||
* {@link AsyncUtils#loop(TypeSpec, AsyncLoopFirstActionProduces, TypeSpec, AsyncLoopSecondActionConsumes)}
|
||||
* or
|
||||
* {@link AsyncUtils#each(TypeSpec, Iterator, AsyncLoopFirstActionConsumesAndProduces, TypeSpec, AsyncLoopSecondActionConsumes)}.
|
||||
* If this is already a two-action loop, then consider nesting
|
||||
* {@link AsyncUtils#sequence(TypeSpec)} in a single-action loop.
|
||||
*
|
||||
* @param v any value, because it is ignored
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return null
|
||||
*/
|
||||
public default Void repeatIgnore(Object v, Throwable exc) {
|
||||
return repeat(null, exc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-execute the loop
|
||||
*/
|
||||
public default void repeat() {
|
||||
repeat(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-execute the loop conditionally
|
||||
*
|
||||
* @param b the value of the predicate
|
||||
*/
|
||||
public default void repeatWhile(boolean b) {
|
||||
repeatWhile(b, null);
|
||||
}
|
||||
}
|
|
@ -1,31 +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.async.loop;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
|
||||
/**
|
||||
* The interface for the only action of certain loops
|
||||
*
|
||||
* @see AsyncUtils#loop(ghidra.async.TypeSpec, AsyncLoopSecondActionRuns)
|
||||
*
|
||||
* @param <R> the type of result for the whole loop
|
||||
*/
|
||||
public interface AsyncLoopOnlyActionRuns<R> extends Consumer<AsyncLoopHandlerForSecond<R>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,40 +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.async.loop;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
|
||||
/**
|
||||
* The interface for the second or only action of certain loops
|
||||
*
|
||||
* @see AsyncUtils#loop(ghidra.async.TypeSpec, AsyncLoopFirstActionProduces,
|
||||
* ghidra.async.TypeSpec, AsyncLoopSecondActionConsumes)
|
||||
* @see AsyncUtils#each(ghidra.async.TypeSpec, java.util.Iterator,
|
||||
* AsyncLoopFirstActionConsumesAndProduces, ghidra.async.TypeSpec,
|
||||
* AsyncLoopSecondActionConsumes)
|
||||
* @see AsyncUtils#each(ghidra.async.TypeSpec, java.util.Iterator,
|
||||
* AsyncLoopSecondActionConsumes)
|
||||
*
|
||||
* @param <R> the type of result for the whole loop
|
||||
* @param <T> the type of object consumed, i.e., provided by the controlling iterator or the
|
||||
* subordinate asynchronous task
|
||||
*/
|
||||
public interface AsyncLoopSecondActionConsumes<R, T>
|
||||
extends BiConsumer<T, AsyncLoopHandlerForSecond<R>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,35 +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.async.seq;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
|
||||
/**
|
||||
* The interface for an action that consumes a temporary value but produces nothing
|
||||
*
|
||||
* @see AsyncSequenceWithTemp#then(AsyncSequenceActionConsumes)
|
||||
* @see AsyncUtils#sequence(TypeSpec)
|
||||
*
|
||||
* @param <R> the type of result of the whole sequence
|
||||
* @param <T> the type of temporary consumed, i.e., produced by the previous action
|
||||
*/
|
||||
public interface AsyncSequenceActionConsumes<R, T>
|
||||
extends BiConsumer<T, AsyncSequenceHandlerForRunner<R>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,38 +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.async.seq;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
|
||||
/**
|
||||
* The interface for an action that consumes and produces temporary values
|
||||
*
|
||||
* @see AsyncSequenceWithTemp#then(AsyncSequenceActionConsumesAndProduces, TypeSpec)
|
||||
* @see AsyncSequenceWithTemp#then(AsyncSequenceActionConsumesAndProduces, AtomicReference)
|
||||
* @see AsyncUtils#sequence(TypeSpec)
|
||||
*
|
||||
* @param <R> the type of result of the whole sequence
|
||||
* @param <T> the type of temporary consumed, i.e., produced by the previous action
|
||||
* @param <U> the type of temporary produced, i.e., consumed by the following action
|
||||
*/
|
||||
public interface AsyncSequenceActionConsumesAndProduces<R, T, U>
|
||||
extends BiConsumer<T, AsyncSequenceHandlerForProducer<R, U>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,37 +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.async.seq;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
|
||||
/**
|
||||
* The interface for an action that consumes nothing but produces a temporary value
|
||||
*
|
||||
* @see AsyncSequenceWithoutTemp#then(AsyncSequenceActionProduces, TypeSpec)
|
||||
* @see AsyncSequenceWithoutTemp#then(AsyncSequenceActionProduces, AtomicReference)
|
||||
* @see AsyncUtils#sequence(TypeSpec)
|
||||
*
|
||||
* @param <R> the type of result of the whole sequence
|
||||
* @param <U> the type of temporary produced, i.e., consumed by the following action
|
||||
*/
|
||||
public interface AsyncSequenceActionProduces<R, U>
|
||||
extends Consumer<AsyncSequenceHandlerForProducer<R, U>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,33 +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.async.seq;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.async.TypeSpec;
|
||||
|
||||
/**
|
||||
* The interface for an action that neither consumes nor produces temporary values
|
||||
*
|
||||
* @see AsyncSequenceWithoutTemp#then(AsyncSequenceActionRuns)
|
||||
* @see AsyncUtils#sequence(TypeSpec)
|
||||
*
|
||||
* @param <R> the type produced by the whole sequence
|
||||
*/
|
||||
public interface AsyncSequenceActionRuns<R> extends Consumer<AsyncSequenceHandlerForRunner<R>> {
|
||||
// Nothing
|
||||
}
|
|
@ -1,49 +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.async.seq;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import ghidra.async.AsyncHandlerCanExit;
|
||||
|
||||
/**
|
||||
* The handler given to sequence actions that produce a temporary value
|
||||
*
|
||||
* @param <R> the type of result of the whole sequence
|
||||
* @param <U> the type of temporary produced
|
||||
*/
|
||||
public interface AsyncSequenceHandlerForProducer<R, U> extends AsyncHandlerCanExit<R> {
|
||||
/**
|
||||
* Execute the next action or complete the sequence with null
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)}. This should rarely if ever be invoked directly
|
||||
* since it is most often used to handle future completion of a subordinate asynchronous task.
|
||||
* If it is invoked directly, consider merging this action with the following one. If this is
|
||||
* the final action, consider using {@link #exit(Object)} instead, especially to provide a
|
||||
* non-null result.
|
||||
*
|
||||
* If the subordinate completes without exception, the next action is executed with the result.
|
||||
* If this is the final action, the sequence is completed with {@code null}. If it completes
|
||||
* exceptionally, then the whole sequence completes exceptionally and terminates.
|
||||
*
|
||||
* @param result the result of completion, producing the temporary value
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return null
|
||||
*/
|
||||
public Void next(U result, Throwable exc);
|
||||
}
|
|
@ -1,64 +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.async.seq;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import ghidra.async.*;
|
||||
|
||||
public interface AsyncSequenceHandlerForRunner<R> extends AsyncHandlerCanExit<R> {
|
||||
/**
|
||||
* Execute the next action or complete the sequence with null
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)}. This should rarely if ever be invoked directly
|
||||
* since it is most often used to handle future completion of a subordinate asynchronous task.
|
||||
* If it is invoked directly, consider merging this action with the following one. If this is
|
||||
* the final action, consider using {@link #exit(Object)} instead, especially to provide a
|
||||
* non-null result.
|
||||
*
|
||||
* If the subordinate completes without exception, the next action is executed. If this is the
|
||||
* final action, the sequence is completed with {@code null}. If it completes exceptionally,
|
||||
* then the whole sequence completes exceptionally and terminates.
|
||||
*
|
||||
* @param v null placeholder for {@link Void}
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return null
|
||||
*/
|
||||
public Void next(Void v, Throwable exc);
|
||||
|
||||
/**
|
||||
* Do like {@link #next(Void, Throwable)}, but ignore the result of a subordinate task
|
||||
*
|
||||
* This method is suitable for passing by reference to
|
||||
* {@link CompletableFuture#handle(BiFunction)} for any type. There is not need to invoke this
|
||||
* method directly. If the subordinate asynchronous task produces a result, and that result does
|
||||
* not need to be consumed by the following action, this method must be used, since
|
||||
* {@link #next(Void, Throwable)} requires the {@link CompletableFuture} to provide a
|
||||
* {@link Void} result. If the result cannot be ignored, then the following action must accept
|
||||
* the result, or the result must be stored in an {@link AtomicReference}. See
|
||||
* {@link AsyncUtils#sequence(TypeSpec)}.
|
||||
*
|
||||
* @param result the result to ignore
|
||||
* @param exc the exception if completed exceptionally
|
||||
* @return null
|
||||
*/
|
||||
public default Void nextIgnore(Object result, Throwable exc) {
|
||||
return next(null, exc);
|
||||
}
|
||||
}
|
|
@ -1,307 +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.async.seq;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import ghidra.async.*;
|
||||
|
||||
/**
|
||||
* Part of the underlying implementation of {@link AsyncUtils#sequence(TypeSpec)}
|
||||
*
|
||||
* @param <R> the type of result for the whole sequence
|
||||
* @param <T> the type of temporary value produced by the current final action
|
||||
*/
|
||||
public class AsyncSequenceWithTemp<R, T> {
|
||||
/**
|
||||
* Common handler implementation
|
||||
*
|
||||
* @param <R> the type of result for the shole sequence
|
||||
* @param <T> the type of temporary value produced
|
||||
*/
|
||||
static abstract class AbstractHandler<R, T> implements AsyncHandlerCanExit<R> {
|
||||
final CompletableFuture<R> seqResult;
|
||||
final CompletableFuture<T> future = new CompletableFuture<>();
|
||||
|
||||
public AbstractHandler(CompletableFuture<R> seqResult) {
|
||||
this.seqResult = seqResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void exit(R result, Throwable exc) {
|
||||
if (exc != null) {
|
||||
seqResult.completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
seqResult.complete(result);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler given to actions that produce or store a temporary value
|
||||
*
|
||||
* @param <R> the type of result for the whole sequence
|
||||
* @param <T> the type of value produced or stored
|
||||
* @param <U> the type of temporary value produced -- {@link Void} for actions that store
|
||||
*/
|
||||
static abstract class AbstractHandlerForProducer<R, T, U> extends AbstractHandler<R, U>
|
||||
implements AsyncSequenceHandlerForProducer<R, T> {
|
||||
public AbstractHandlerForProducer(CompletableFuture<R> seqResult) {
|
||||
super(seqResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void next(T result, Throwable exc) {
|
||||
if (exc != null) {
|
||||
seqResult.completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
proceedWithoutException(result);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the portion of {@link #next(Object, Throwable)} to execute when the result of
|
||||
* the subordinate task is not exceptional
|
||||
*
|
||||
* @param result the result of the subordinate task
|
||||
*/
|
||||
protected abstract void proceedWithoutException(T result);
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler given to actions that produce a temporary value
|
||||
*
|
||||
* @param <R> the type of result for the shole sequence
|
||||
* @param <T> the type of temporary value produced
|
||||
*/
|
||||
static class HandlerForProducer<R, T> extends AbstractHandlerForProducer<R, T, T> {
|
||||
public HandlerForProducer(CompletableFuture<R> seqResult) {
|
||||
super(seqResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void proceedWithoutException(T futureResult) {
|
||||
future.complete(futureResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler given to actions that store a value
|
||||
*
|
||||
* @param <R> the type of result for the shole sequence
|
||||
* @param <T> the type of value stored
|
||||
*/
|
||||
static class HandlerForStorer<R, T> extends AbstractHandlerForProducer<R, T, Void> {
|
||||
private final AtomicReference<T> storage;
|
||||
|
||||
public HandlerForStorer(CompletableFuture<R> seqResult, AtomicReference<T> storage) {
|
||||
super(seqResult);
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void proceedWithoutException(T futureResult) {
|
||||
storage.set(futureResult);
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler given to actions that do not produce a value
|
||||
*
|
||||
* @param <R> the type of result for the whole sequence
|
||||
*/
|
||||
static class HandlerForRunner<R> extends AbstractHandler<R, Void>
|
||||
implements AsyncSequenceHandlerForRunner<R> {
|
||||
public HandlerForRunner(CompletableFuture<R> seqResult) {
|
||||
super(seqResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void next(Void result, Throwable exc) {
|
||||
if (exc != null) {
|
||||
seqResult.completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
future.complete(result);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// The temporary result
|
||||
private final CompletableFuture<T> tmpResult;
|
||||
// The result for the whole sequence
|
||||
private final CompletableFuture<R> seqResult;
|
||||
|
||||
AsyncSequenceWithTemp(CompletableFuture<R> seqResult, CompletableFuture<T> tmpResult) {
|
||||
this.seqResult = seqResult;
|
||||
this.tmpResult = tmpResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to this sequence an action that consumes the temporary value and produces another
|
||||
*
|
||||
* @param action the action
|
||||
* @param type the type of temporary value the action will produce
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithTemp<R, U> then(
|
||||
AsyncSequenceActionConsumesAndProduces<R, T, U> action, TypeSpec<U> type) {
|
||||
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenCompose((result) -> {
|
||||
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
|
||||
try {
|
||||
action.accept(result, handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to this sequence an action that consumes the temporary value and produces another
|
||||
*
|
||||
* @param executor the thread pool for this action
|
||||
* @param action the action
|
||||
* @param type the type of temporary value the action will produce
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithTemp<R, U> then(Executor executor,
|
||||
AsyncSequenceActionConsumesAndProduces<R, T, U> action, TypeSpec<U> type) {
|
||||
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
|
||||
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
|
||||
try {
|
||||
action.accept(result, handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}, executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to this sequence an action that consumes the temporary value and stores another
|
||||
*
|
||||
* @param action the action
|
||||
* @param storage a reference to receive the result upon completion
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithoutTemp<R> then(
|
||||
AsyncSequenceActionConsumesAndProduces<R, T, U> action, AtomicReference<U> storage) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
|
||||
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
|
||||
try {
|
||||
action.accept(result, handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to this sequence an action that consumes the temporary value and stores another
|
||||
*
|
||||
* @param executor the thread pool for this action
|
||||
* @param action the action
|
||||
* @param storage a reference to receive the result upon completion
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithoutTemp<R> then(Executor executor,
|
||||
AsyncSequenceActionConsumesAndProduces<R, T, U> action, AtomicReference<U> storage) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
|
||||
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
|
||||
try {
|
||||
action.accept(result, handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}, executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to this sequence an action that consumes the temporary value
|
||||
*
|
||||
* @param action the action
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public AsyncSequenceWithoutTemp<R> then(AsyncSequenceActionConsumes<R, T> action) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
|
||||
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
|
||||
try {
|
||||
action.accept(result, handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to this sequence an action that consumes the temporary value
|
||||
*
|
||||
* @param executor the thread pool for this action
|
||||
* @param action the action
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public AsyncSequenceWithoutTemp<R> then(Executor executor,
|
||||
AsyncSequenceActionConsumes<R, T> action) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
|
||||
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
|
||||
try {
|
||||
action.accept(result, handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}, executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an action to execute on sequence completion
|
||||
*
|
||||
* @see AsyncSequenceWithoutTemp#onExit(BiConsumer)
|
||||
* @param action the action to execute
|
||||
*/
|
||||
public AsyncSequenceWithTemp<R, T> onExit(BiConsumer<? super R, Throwable> action) {
|
||||
seqResult.handle((result, exc) -> {
|
||||
action.accept(result, exc);
|
||||
return result;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,228 +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.async.seq;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import ghidra.async.*;
|
||||
import ghidra.async.seq.AsyncSequenceWithTemp.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Part of the underlying implementation of {@link AsyncUtils#sequence(TypeSpec)}
|
||||
*
|
||||
* @param <R> the type of result for the whole sequence
|
||||
*/
|
||||
public class AsyncSequenceWithoutTemp<R> {
|
||||
// The temporary "result" -- will be null, but notified upon completion
|
||||
private final CompletableFuture<Void> tmpResult;
|
||||
// The result for the whole sequence
|
||||
private final CompletableFuture<R> seqResult;
|
||||
|
||||
/**
|
||||
* Construct a new sequence without a temporary value
|
||||
*
|
||||
* <p>
|
||||
* Do not call this directly. Please use {@link AsyncUtils#sequence(TypeSpec)}.
|
||||
*
|
||||
* @param seqResult the result of the whole sequence, passed to each appended sequence
|
||||
* @param tmpResult the result of the current final action
|
||||
*/
|
||||
public AsyncSequenceWithoutTemp(CompletableFuture<R> seqResult,
|
||||
CompletableFuture<Void> tmpResult) {
|
||||
this.seqResult = seqResult;
|
||||
this.tmpResult = tmpResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an action to this sequence that produces a temporary value
|
||||
*
|
||||
* @param action the action
|
||||
* @param type the type of temporary value that action will produce
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithTemp<R, U> then(AsyncSequenceActionProduces<R, U> action,
|
||||
TypeSpec<U> type) {
|
||||
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenCompose((result) -> {
|
||||
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
|
||||
try {
|
||||
action.accept(handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an action to this sequence that produces a temporary value
|
||||
*
|
||||
* @param action the action
|
||||
* @param type the type of temporary value that action will produce
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithTemp<R, U> then(Executor executor,
|
||||
AsyncSequenceActionProduces<R, U> action, TypeSpec<U> type) {
|
||||
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
|
||||
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
|
||||
try {
|
||||
action.accept(handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}, executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an action to this sequence that stores a value
|
||||
*
|
||||
* @param action the action
|
||||
* @param storage a reference to receive the result upon completion
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithoutTemp<R> then(AsyncSequenceActionProduces<R, U> action,
|
||||
AtomicReference<U> storage) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
|
||||
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
|
||||
try {
|
||||
action.accept(handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an action to this sequence that stores a value
|
||||
*
|
||||
* @param action the action
|
||||
* @param storage a reference to receive the result upon completion
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public <U> AsyncSequenceWithoutTemp<R> then(Executor executor,
|
||||
AsyncSequenceActionProduces<R, U> action, AtomicReference<U> storage) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
|
||||
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
|
||||
try {
|
||||
action.accept(handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}, executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an action to this sequence
|
||||
*
|
||||
* @param action the action
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public AsyncSequenceWithoutTemp<R> then(AsyncSequenceActionRuns<R> action) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
|
||||
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
|
||||
try {
|
||||
action.accept(handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an action to this sequence
|
||||
*
|
||||
* @param action the action
|
||||
* @return the new sequence with the appended action
|
||||
*/
|
||||
public AsyncSequenceWithoutTemp<R> then(Executor executor, AsyncSequenceActionRuns<R> action) {
|
||||
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
|
||||
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
|
||||
try {
|
||||
action.accept(handler);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
seqResult.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
return handler.future;
|
||||
}, executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish defining this sequence of actions and obtain its future result
|
||||
*
|
||||
* <p>
|
||||
* When an action in the sequence calls {@link AsyncHandlerCanExit#exit(Object, Throwable)}, the
|
||||
* returned {@link CompletableFuture} is completed. If any action completes exceptionally, the
|
||||
* returned {@link CompletableFuture} is completed exceptionally. If the final action executes,
|
||||
* {@link AsyncSequenceHandlerForRunner#next(Void, Throwable)}, the returned
|
||||
* {@link CompletableFuture} is completed with {@code null}.
|
||||
*
|
||||
* @return the future result of the sequence
|
||||
*/
|
||||
public CompletableFuture<R> finish() {
|
||||
return then((seq) -> {
|
||||
seq.exit(null, null);
|
||||
}).seqResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an action to execute on sequence completion
|
||||
*
|
||||
* <p>
|
||||
* All registered actions are submitted for execution simultaneously when an action in the
|
||||
* sequence calls {@link AsyncHandlerCanExit#exit(Object, Throwable)}. This is useful for
|
||||
* methods that begin executing sequences "with a context". It is roughly equivalent to a
|
||||
* {@code finally} block. On-exit actions can be registered before other actions are appended to
|
||||
* the chain.
|
||||
*
|
||||
* <p>
|
||||
* If the sequence completes exceptionally, that exception is passed to the action. This method
|
||||
* cannot be used to handle the exception, since this method returns this same sequence, not the
|
||||
* resulting one. An uncaught exception in an on-exit action will simply be logged and ignored.
|
||||
*
|
||||
* @param action the action to execute
|
||||
*/
|
||||
public AsyncSequenceWithoutTemp<R> onExit(BiConsumer<? super R, Throwable> action) {
|
||||
seqResult.handle((result, exc) -> {
|
||||
try {
|
||||
action.accept(result, exc);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
Msg.error(this, "Uncaught exception in onExit", t);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,102 +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.comm.packet;
|
||||
|
||||
import static ghidra.async.AsyncUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousByteChannel;
|
||||
import java.nio.channels.CompletionHandler;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import ghidra.async.TypeSpec;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
/**
|
||||
* A wrapper for an {@link AsynchronousByteChannel} that logs the transferred data.
|
||||
*/
|
||||
public class DebugByteChannel implements AsynchronousByteChannel {
|
||||
private final AsynchronousByteChannel wrapped;
|
||||
|
||||
/**
|
||||
* Wrap the given channel
|
||||
*
|
||||
* @param wrapped the channel to wrap
|
||||
*/
|
||||
public DebugByteChannel(AsynchronousByteChannel wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
wrapped.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return wrapped.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A> void read(ByteBuffer dst, A attachment,
|
||||
CompletionHandler<Integer, ? super A> handler) {
|
||||
int start = dst.position();
|
||||
CompletableFuture<Integer> future = sequence(TypeSpec.INT).then((seq) -> {
|
||||
completable(TypeSpec.INT, wrapped::read, dst).handle(seq::next);
|
||||
}, TypeSpec.INT).then((len, seq) -> {
|
||||
if (len == -1) {
|
||||
Msg.debug(this, "Read EOF");
|
||||
}
|
||||
else {
|
||||
byte[] data = new byte[len];
|
||||
dst.position(start);
|
||||
dst.get(data);
|
||||
Msg.debug(this, "Read: " + NumericUtilities.convertBytesToString(data));
|
||||
}
|
||||
seq.exit(len, null);
|
||||
}).finish();
|
||||
handle(future, attachment, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Integer> read(ByteBuffer dst) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A> void write(ByteBuffer src, A attachment,
|
||||
CompletionHandler<Integer, ? super A> handler) {
|
||||
int start = src.position();
|
||||
CompletableFuture<Integer> future = sequence(TypeSpec.INT).then((seq) -> {
|
||||
completable(TypeSpec.INT, wrapped::write, src).handle(seq::next);
|
||||
}, TypeSpec.INT).then((len, seq) -> {
|
||||
byte[] data = new byte[len];
|
||||
src.position(start);
|
||||
src.get(data);
|
||||
Msg.debug(this, "Wrote: " + NumericUtilities.convertBytesToString(data));
|
||||
seq.exit(len, null);
|
||||
}).finish();
|
||||
handle(future, attachment, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Integer> write(ByteBuffer src) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -1,49 +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.comm.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.AsynchronousSocketChannel;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public abstract class AbstractAsyncClientHandler<S extends AbstractAsyncServer<S, H>, H extends AbstractAsyncClientHandler<S, H>> {
|
||||
protected final S server;
|
||||
protected final AsynchronousSocketChannel sock;
|
||||
|
||||
public AbstractAsyncClientHandler(S server, AsynchronousSocketChannel sock) {
|
||||
this.server = server;
|
||||
this.sock = sock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the client connection
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
protected void close() throws IOException {
|
||||
sock.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the request processing loop
|
||||
*
|
||||
* The loop executes until the client connection is lost, at which point the future must be
|
||||
* completed. The server will automatically remove the handler when the future completes.
|
||||
*
|
||||
* @return a future which completes when the processing loop terminates
|
||||
*/
|
||||
protected abstract CompletableFuture<Void> launchAsync();
|
||||
}
|
|
@ -1,151 +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.comm.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.async.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public abstract class AbstractAsyncServer<S extends AbstractAsyncServer<S, H>, H extends AbstractAsyncClientHandler<S, H>> {
|
||||
// One thread for all clients... OK since generally only one is connected.
|
||||
private final AsynchronousChannelGroup group =
|
||||
AsynchronousChannelGroup.withThreadPool(Executors.newSingleThreadExecutor());
|
||||
private final AsynchronousServerSocketChannel ssock;
|
||||
//private final SocketAddress addr;
|
||||
|
||||
protected final Set<H> handlers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
public AbstractAsyncServer(SocketAddress addr) throws IOException {
|
||||
//this.addr = addr;
|
||||
ssock = AsynchronousServerSocketChannel.open(group);
|
||||
ssock.bind(addr);
|
||||
}
|
||||
|
||||
private CompletableFuture<AsynchronousSocketChannel> accept() {
|
||||
return AsyncUtils.completable(TypeSpec.cls(AsynchronousSocketChannel.class), ssock::accept);
|
||||
}
|
||||
|
||||
protected abstract boolean checkAcceptable(AsynchronousSocketChannel sock);
|
||||
|
||||
/**
|
||||
* Create a new handler for the given incoming client connection
|
||||
*
|
||||
* @param sock the new client connection
|
||||
* @return a handler for the client
|
||||
*/
|
||||
protected abstract H newHandler(AsynchronousSocketChannel sock);
|
||||
|
||||
protected void removeHandler(H handler) {
|
||||
handlers.remove(handler);
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> launchAsyncService() {
|
||||
return AsyncUtils.loop(TypeSpec.VOID, loop -> {
|
||||
if (ssock.isOpen()) {
|
||||
accept().handle(loop::consume);
|
||||
}
|
||||
else {
|
||||
loop.exit();
|
||||
}
|
||||
}, TypeSpec.cls(AsynchronousSocketChannel.class), (sock, loop) -> {
|
||||
loop.repeat();
|
||||
if (!checkAcceptable(sock)) {
|
||||
try {
|
||||
sock.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "Failed to close rejected connection", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
H handler = newHandler(sock);
|
||||
handlers.add(handler);
|
||||
handler.launchAsync().thenAccept(v -> {
|
||||
removeHandler(handler);
|
||||
}).exceptionally(e -> {
|
||||
Msg.error("Client handler terminated unexpectedly", e);
|
||||
removeHandler(handler);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public SocketAddress getLocalAddress() {
|
||||
try {
|
||||
return ssock.getLocalAddress();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the server socket and all current client connections
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public void terminate() throws IOException {
|
||||
IOException err = null;
|
||||
try {
|
||||
ssock.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
err = e;
|
||||
}
|
||||
for (H h : handlers) {
|
||||
try {
|
||||
h.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (err == null) {
|
||||
err = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
group.shutdown();
|
||||
if (err != null) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept no more connections, but continue servicing existing connections
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
protected void closeServerSocket() throws IOException {
|
||||
ssock.close();
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> allHandlers(Function<H, CompletableFuture<?>> action) {
|
||||
AsyncFence fence = new AsyncFence();
|
||||
for (H h : handlers) {
|
||||
CompletableFuture<?> future = action.apply(h);
|
||||
if (future != null) {
|
||||
fence.include(future);
|
||||
}
|
||||
}
|
||||
return fence.ready();
|
||||
}
|
||||
}
|
|
@ -1,415 +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.comm.util;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.IteratorUtils;
|
||||
|
||||
/**
|
||||
* A set of enumeration constants encoded using bits
|
||||
*
|
||||
* All constants in the set must belong to the same enumeration called the "universe." The
|
||||
* enumeration must implement {@link BitmaskUniverse}, so that each constant provides its mask bit.
|
||||
* This is essentially a "set" abstraction on the idiom of using "flag" bits to represent the
|
||||
* present or absence of each element.
|
||||
*
|
||||
* It is highly recommended that each constant's mask have a population of one bit. At the very
|
||||
* least, each should have one unique bit. Constants which represent combinations of other flags are
|
||||
* allowed, but they should be documented. Defining such combinations may produce surprising
|
||||
* behavior from the perspective of the {@link Set} interface. For instance, consider constants
|
||||
* {@code VAL_A}, {@code VAL_B}, and {@code VALS_A_B}. Adding {@code VAL_A} will cause no surprise,
|
||||
* but subsequently adding {@code VAL_B} will cause {@code VALS_A_B} to materialize, even though it
|
||||
* was never added explicitly. So long as the calling methods do not expect strict set behavior,
|
||||
* this is OK. If there exists a constant which defines zero flags, then the behavior is undefined.
|
||||
* In general, the element will always be present, even though {@link #isEmpty()} may return
|
||||
* {@code true}.
|
||||
*
|
||||
* @param <E> the type of enumeration constant elements
|
||||
*/
|
||||
public class BitmaskSet<E extends Enum<E> & BitmaskUniverse> implements Set<E> {
|
||||
|
||||
/**
|
||||
* Obtain a set of the given constants
|
||||
*
|
||||
* @param elements the constants, all from the same enumeration
|
||||
* @return the set
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <E extends Enum<E> & BitmaskUniverse> BitmaskSet<E> of(E... elements) {
|
||||
long bitmask = 0;
|
||||
for (E elem : elements) {
|
||||
bitmask |= elem.getMask();
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> universe = (Class<E>) elements.getClass().getComponentType();
|
||||
return new BitmaskSet<>(universe, bitmask);
|
||||
}
|
||||
|
||||
private final Class<E> universe;
|
||||
private long bitmask;
|
||||
|
||||
/**
|
||||
* Decode a set of constants from the given universe using the given bitmask
|
||||
*
|
||||
* @param universe the enumeration of constants the set may contain
|
||||
* @param bitmask the bitmask to decode
|
||||
*/
|
||||
public BitmaskSet(Class<E> universe, long bitmask) {
|
||||
this.universe = universe;
|
||||
this.bitmask = bitmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the given collection as a bitmask of constants
|
||||
*
|
||||
* @param universe the enumeration of constants the set may contain
|
||||
* @param collection the collection to copy
|
||||
*/
|
||||
public BitmaskSet(Class<E> universe, Collection<E> collection) {
|
||||
this.universe = universe;
|
||||
|
||||
// Use bitmasking shortcut, if possible
|
||||
BitmaskSet<E> that = castSameType(collection);
|
||||
if (that != null) {
|
||||
this.bitmask = that.bitmask;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, do it the long way...
|
||||
if (collection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (E elem : collection) {
|
||||
bitmask |= elem.getMask();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the given bitmask set
|
||||
*
|
||||
* @param that the other set
|
||||
*/
|
||||
public BitmaskSet(BitmaskSet<E> that) {
|
||||
this.universe = that.universe;
|
||||
this.bitmask = that.bitmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty set
|
||||
*
|
||||
* @param universe the enumeration of constants the set may contain
|
||||
*/
|
||||
public BitmaskSet(Class<E> universe) {
|
||||
this.universe = universe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a constant is in the set
|
||||
*
|
||||
* @param elem the constant
|
||||
* @return {@code true} if it is present, {@code false} otherwise
|
||||
*/
|
||||
protected boolean containsImpl(E elem) {
|
||||
long mask = elem.getMask();
|
||||
return (mask & bitmask) == mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a constant from the set
|
||||
*
|
||||
* @param elem the constant to remove
|
||||
* @return {@code true} if it was present and removed, {@code false} if already not present
|
||||
*/
|
||||
protected boolean removeImpl(E elem) {
|
||||
long old = bitmask;
|
||||
bitmask &= ~elem.getMask();
|
||||
return old != bitmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to cast the given collection as a bitmask of the same type of elements as this
|
||||
*
|
||||
* @param c the collection to cast
|
||||
* @return the same collection, or {@code null} if the collection or element types differ
|
||||
*/
|
||||
protected BitmaskSet<E> castSameType(Collection<?> c) {
|
||||
if (!(c instanceof BitmaskSet)) {
|
||||
return null;
|
||||
}
|
||||
BitmaskSet<?> bm = (BitmaskSet<?>) c;
|
||||
if (this.universe != bm.universe) {
|
||||
return null;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
BitmaskSet<E> bme = (BitmaskSet<E>) bm;
|
||||
return bme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
int count = 0;
|
||||
for (E elem : universe.getEnumConstants()) {
|
||||
if (containsImpl(elem)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return bitmask == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (universe != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
E elem = (E) o;
|
||||
return containsImpl(elem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
List<E> all = Arrays.asList(universe.getEnumConstants());
|
||||
return IteratorUtils.filteredIterator(all.iterator(), this::containsImpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return toArray(new Object[] {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
List<Object> arr = new ArrayList<>();
|
||||
for (E elem : universe.getEnumConstants()) {
|
||||
if (containsImpl(elem)) {
|
||||
arr.add(elem);
|
||||
}
|
||||
}
|
||||
return arr.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E elem) {
|
||||
long old = bitmask;
|
||||
bitmask |= elem.getMask();
|
||||
return old != bitmask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if (universe != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
E elem = (E) o;
|
||||
return removeImpl(elem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
if (c.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use bitmasking shortcut, if possible
|
||||
BitmaskSet<E> that = castSameType(c);
|
||||
if (that != null) {
|
||||
return (this.bitmask | that.bitmask) == this.bitmask;
|
||||
}
|
||||
|
||||
// Otherwise, do it the long way...
|
||||
for (Object o : c) {
|
||||
if (!contains(o)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean containsAny(Collection<?> c) {
|
||||
if (c.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use bitmasking shortcut, if possible
|
||||
BitmaskSet<E> that = castSameType(c);
|
||||
if (that != null) {
|
||||
return (this.bitmask & that.bitmask) != 0;
|
||||
}
|
||||
|
||||
// Otherwise, do it the long way...
|
||||
for (Object o : c) {
|
||||
if (contains(o)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
long old = this.bitmask;
|
||||
|
||||
// Use bitmasking shortcut, if possible
|
||||
BitmaskSet<E> that = castSameType(c);
|
||||
if (that != null) {
|
||||
this.bitmask |= that.bitmask;
|
||||
return old != this.bitmask;
|
||||
}
|
||||
|
||||
if (c.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, do it the long way...
|
||||
for (E elem : c) {
|
||||
this.bitmask |= elem.getMask();
|
||||
}
|
||||
return old != this.bitmask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
long old = this.bitmask;
|
||||
if (c.isEmpty()) {
|
||||
this.bitmask = 0;
|
||||
return old != this.bitmask;
|
||||
}
|
||||
|
||||
// Use bitmasking shortcut, if possible
|
||||
BitmaskSet<E> that = castSameType(c);
|
||||
if (that != null) {
|
||||
this.bitmask &= that.bitmask;
|
||||
return old != this.bitmask;
|
||||
}
|
||||
|
||||
// Otherwise, do it the long way... Build the bitmask manually
|
||||
long toKeep = 0;
|
||||
for (Object o : c) {
|
||||
if (universe == o.getClass()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
E elem = (E) o;
|
||||
toKeep |= elem.getMask();
|
||||
}
|
||||
}
|
||||
this.bitmask &= toKeep;
|
||||
return old != this.bitmask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
if (c.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
long old = this.bitmask;
|
||||
|
||||
// Use bitmasking shortcut, if possible
|
||||
BitmaskSet<E> that = castSameType(c);
|
||||
if (that != null) {
|
||||
this.bitmask &= ~that.bitmask;
|
||||
return old != this.bitmask;
|
||||
}
|
||||
|
||||
// Otherwise, do it the long way... Build the bitmask manually
|
||||
long toRemove = 0;
|
||||
for (Object o : c) {
|
||||
if (universe == o.getClass()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
E elem = (E) o;
|
||||
toRemove |= elem.getMask();
|
||||
}
|
||||
}
|
||||
this.bitmask &= ~toRemove;
|
||||
return old != this.bitmask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.bitmask = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Collection)) {
|
||||
return false;
|
||||
}
|
||||
Collection<?> col = (Collection<?>) obj;
|
||||
|
||||
// Use bitmasking shortcut, if possible
|
||||
BitmaskSet<E> that = castSameType(col);
|
||||
if (that != null) {
|
||||
return this.bitmask == that.bitmask;
|
||||
}
|
||||
|
||||
// Otherwise, do it the long way...
|
||||
return this.containsAll(col) && col.containsAll(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(bitmask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the encoded bitmask
|
||||
*
|
||||
* @return the bitmask
|
||||
*/
|
||||
public long getBitmask() {
|
||||
return bitmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the given bitmask, overwriting the value of this set
|
||||
*
|
||||
* @param bitmask the bitmask to decode
|
||||
*/
|
||||
public void setBitmask(long bitmask) {
|
||||
this.bitmask = bitmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this set's universe
|
||||
*
|
||||
* @return the enumeration representing the universe
|
||||
*/
|
||||
public Class<E> getUniverse() {
|
||||
return universe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
boolean first = true;
|
||||
for (E elem : this) {
|
||||
if (first) {
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(elem);
|
||||
}
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -1,30 +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.comm.util;
|
||||
|
||||
/**
|
||||
* The interface for enumerations usable with {@link BitmaskSet}
|
||||
*/
|
||||
public interface BitmaskUniverse {
|
||||
/**
|
||||
* Get the bit associated with this constant
|
||||
*
|
||||
* It ought to have a populating of one, and be unique from the other constants.
|
||||
*
|
||||
* @return the mask bit
|
||||
*/
|
||||
long getMask();
|
||||
}
|
|
@ -1,336 +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.async;
|
||||
|
||||
import static ghidra.async.AsyncUtils.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class AsyncLockTest {
|
||||
|
||||
private AsyncLock lock; // Placeholder for example
|
||||
|
||||
private CompletableFuture<Void> doCriticalStuff() {
|
||||
// Placeholder for example
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> doMoreCriticalStuff() {
|
||||
// Placeholder for example
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> fetchValue() {
|
||||
// Placeholder for example
|
||||
return CompletableFuture.completedFuture(3);
|
||||
}
|
||||
|
||||
public CompletableFuture<String> convertValue(int val) {
|
||||
return CompletableFuture.completedFuture(Integer.toString(val));
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> exampleSeq() {
|
||||
return sequence(TypeSpec.VOID).then((seq) -> {
|
||||
fetchValue().handle(seq::next);
|
||||
}, TypeSpec.INT).then((val, seq) -> {
|
||||
convertValue(val + 10).handle(seq::next);
|
||||
}, TypeSpec.STRING).then((str, seq) -> {
|
||||
Msg.debug(this, str);
|
||||
seq.exit();
|
||||
}).finish();
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> exampleLock1() {
|
||||
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
|
||||
return sequence(TypeSpec.VOID).then((seq) -> {
|
||||
lock.acquire(null).handle(seq::next);
|
||||
}, hold).then((seq) -> {
|
||||
doCriticalStuff().handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
doMoreCriticalStuff().handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
hold.get().release();
|
||||
seq.exit();
|
||||
}).finish().exceptionally((exc) -> {
|
||||
hold.get().release();
|
||||
return ExceptionUtils.rethrow(exc);
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> exampleLock2() {
|
||||
return lock.with(TypeSpec.VOID, null).then((hold, seq) -> {
|
||||
doCriticalStuff().handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
doMoreCriticalStuff().handle(seq::next);
|
||||
}).finish();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithError() throws Throwable {
|
||||
AsyncLock l = new AsyncLock();
|
||||
int result = l.with(TypeSpec.INT, null).then((own, seq) -> {
|
||||
throw new AssertionError("Blargh");
|
||||
}).finish().exceptionally(exc -> {
|
||||
return 0xdead;
|
||||
}).get(1000, TimeUnit.MILLISECONDS);
|
||||
|
||||
assertEquals(0xdead, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReentry() {
|
||||
// This is very contrived. A real use would pass ownership to some method which cannot
|
||||
// assume that it already holds the lock
|
||||
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
|
||||
AsyncLock l = new AsyncLock();
|
||||
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
|
||||
AtomicReference<AsyncLock.Hold> hold2 = new AtomicReference<>();
|
||||
List<Integer> result = new ArrayList<>();
|
||||
|
||||
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
|
||||
result.add(1);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
l.with(TypeSpec.VOID, hold.get(), hold2).then((seq2) -> {
|
||||
result.add(2);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq2::next);
|
||||
}).finish().handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(3);
|
||||
seq.exit();
|
||||
});
|
||||
|
||||
CompletableFuture<Void> future;
|
||||
while (null != (future = queue.poll())) {
|
||||
future.complete(null);
|
||||
}
|
||||
|
||||
List<Integer> exp = Arrays.asList(new Integer[] { 1, 2, 3 });
|
||||
assertEquals(exp, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("TODO") // Not sure why this fails under Gradle but not my IDE
|
||||
public void testTwoSequencesWithLockAtomic() {
|
||||
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
|
||||
AsyncLock l = new AsyncLock();
|
||||
List<Integer> result = new ArrayList<>();
|
||||
|
||||
l.with(TypeSpec.VOID, null).then((hold, seq) -> {
|
||||
result.add(1);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(2);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(3);
|
||||
seq.exit();
|
||||
});
|
||||
l.with(TypeSpec.VOID, null).then((hold, seq) -> {
|
||||
result.add(4);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(5);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(6);
|
||||
seq.exit();
|
||||
});
|
||||
|
||||
CompletableFuture<Void> future;
|
||||
while (null != (future = queue.poll())) {
|
||||
future.complete(null);
|
||||
}
|
||||
|
||||
List<Integer> exp = Arrays.asList(new Integer[] { 1, 2, 3, 4, 5, 6 });
|
||||
assertEquals(exp, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("TODO") // Not sure why this fails under Gradle but not my IDE
|
||||
public void testTwoSequencesWithReentry() {
|
||||
// This is very contrived. A real use would pass ownership to some method which cannot
|
||||
// assume that it already owns the lock
|
||||
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
|
||||
AsyncLock l = new AsyncLock();
|
||||
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
|
||||
AtomicReference<AsyncLock.Hold> hold2 = new AtomicReference<>();
|
||||
List<Integer> result = new ArrayList<>();
|
||||
|
||||
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
|
||||
result.add(1);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
l.with(TypeSpec.VOID, hold.get(), hold2).then((seq2) -> {
|
||||
result.add(2);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq2::next);
|
||||
}).finish().handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(3);
|
||||
seq.exit();
|
||||
});
|
||||
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
|
||||
result.add(4);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
l.with(TypeSpec.VOID, hold.get(), hold2).then((seq2) -> {
|
||||
result.add(5);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq2::next);
|
||||
}).finish().handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(6);
|
||||
seq.exit();
|
||||
});
|
||||
|
||||
CompletableFuture<Void> future;
|
||||
while (null != (future = queue.poll())) {
|
||||
future.complete(null);
|
||||
}
|
||||
|
||||
List<Integer> exp = Arrays.asList(new Integer[] { 1, 2, 3, 4, 5, 6 });
|
||||
assertEquals(exp, result);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testInvalidHandle() throws Throwable {
|
||||
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
|
||||
AsyncLock l = new AsyncLock();
|
||||
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
|
||||
|
||||
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
/*
|
||||
* NOTE: Using seq::next here fails to release the lock, because #asCompletableFuture()
|
||||
* must be called on the sequence to install the automatic call to ::exit.
|
||||
*/
|
||||
future.handle(seq::exit);
|
||||
});
|
||||
|
||||
// Finish the "critical section"
|
||||
queue.poll().complete(null);
|
||||
|
||||
try {
|
||||
l.with(TypeSpec.VOID, hold.get()).then((drop, seq) -> {
|
||||
seq.exit();
|
||||
}).finish().getNow(null);
|
||||
}
|
||||
catch (CompletionException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testForgottenHandle() throws Throwable {
|
||||
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
|
||||
AsyncLock l = new AsyncLock();
|
||||
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
|
||||
// We have to contrive the forgotten lock, and control garbage collection
|
||||
// It shouldn't matter when gc happens, but for the sake of testing, we want it soon
|
||||
|
||||
sequence(TypeSpec.VOID).then((seq) -> {
|
||||
l.acquire(null).handle(seq::next);
|
||||
}, hold).then((seq) -> {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::exit);
|
||||
});
|
||||
|
||||
// Finish the "critical section"
|
||||
queue.poll().complete(null);
|
||||
Msg.info(this, "The forgotten lock message is expected");
|
||||
// Forget the lock, and wait for it to die
|
||||
hold.set(null);
|
||||
while (!l.dead) {
|
||||
System.gc();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
|
||||
try {
|
||||
l.acquire(null).getNow(null);
|
||||
}
|
||||
catch (CompletionException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrash() throws Exception {
|
||||
|
||||
boolean debug = false;
|
||||
|
||||
// This generates copious log messages; enable only when debugging
|
||||
AsyncLock l = new AsyncLock();
|
||||
if (debug) {
|
||||
l = new AsyncLock("testThrash Lock");
|
||||
}
|
||||
|
||||
var noSync = new Object() {
|
||||
int total = 0;
|
||||
};
|
||||
|
||||
AsyncFence fence = new AsyncFence();
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
final int _i = i;
|
||||
fence.include(l.with(TypeSpec.VOID, null).then((hold, seq) -> {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
if (debug) {
|
||||
Msg.info(this, "i: " + _i);
|
||||
Msg.info(this, "Depth: " + new Throwable().getStackTrace().length);
|
||||
}
|
||||
//assert noSync.total == 0;
|
||||
noSync.total++;
|
||||
}).handle(seq::next);
|
||||
}).then(seq -> {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
noSync.total--;
|
||||
}).handle(seq::next);
|
||||
}).finish());
|
||||
}
|
||||
|
||||
fence.ready().get(5000000, TimeUnit.MILLISECONDS);
|
||||
assert noSync.total == 0;
|
||||
}
|
||||
}
|
|
@ -1,70 +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.async;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.async.AsyncRace;
|
||||
|
||||
public class AsyncRaceTest {
|
||||
@Test
|
||||
public void testAlternateCompleted() {
|
||||
AsyncRace<Integer> race = new AsyncRace<>();
|
||||
race.include(CompletableFuture.completedFuture(1));
|
||||
assertEquals(1, race.next().getNow(null).intValue());
|
||||
race.include(CompletableFuture.completedFuture(2));
|
||||
assertEquals(2, race.next().getNow(null).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoCompleted() {
|
||||
AsyncRace<Integer> race = new AsyncRace<>();
|
||||
race.include(CompletableFuture.completedFuture(1));
|
||||
race.include(CompletableFuture.completedFuture(2));
|
||||
assertEquals(1, race.next().getNow(null).intValue());
|
||||
assertEquals(2, race.next().getNow(null).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoDelayed() {
|
||||
AsyncRace<Integer> race = new AsyncRace<>();
|
||||
CompletableFuture<Integer> c1 = new CompletableFuture<>();
|
||||
CompletableFuture<Integer> c2 = new CompletableFuture<>();
|
||||
race.include(c1);
|
||||
race.include(c2);
|
||||
c1.complete(1);
|
||||
c2.complete(2);
|
||||
assertEquals(1, race.next().getNow(null).intValue());
|
||||
assertEquals(2, race.next().getNow(null).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoDelayedReversed() {
|
||||
AsyncRace<Integer> race = new AsyncRace<>();
|
||||
CompletableFuture<Integer> c1 = new CompletableFuture<>();
|
||||
CompletableFuture<Integer> c2 = new CompletableFuture<>();
|
||||
race.include(c1);
|
||||
race.include(c2);
|
||||
c2.complete(2);
|
||||
c1.complete(1);
|
||||
assertEquals(2, race.next().getNow(null).intValue());
|
||||
assertEquals(1, race.next().getNow(null).intValue());
|
||||
}
|
||||
}
|
|
@ -15,12 +15,8 @@
|
|||
*/
|
||||
package ghidra.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import ghidra.async.AsyncUtils.TemperamentalRunnable;
|
||||
import ghidra.async.AsyncUtils.TemperamentalSupplier;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public interface AsyncTestUtils {
|
||||
|
@ -65,47 +61,4 @@ public interface AsyncTestUtils {
|
|||
});
|
||||
return waitOnNoValidate(validated);
|
||||
}
|
||||
|
||||
default void retryVoid(TemperamentalRunnable runnable,
|
||||
Collection<Class<? extends Throwable>> retriable) throws Throwable {
|
||||
retry(() -> {
|
||||
runnable.run();
|
||||
return null;
|
||||
}, retriable);
|
||||
}
|
||||
|
||||
default <T> T retry(TemperamentalSupplier<T> supplier,
|
||||
Collection<Class<? extends Throwable>> retriable) throws Throwable {
|
||||
return retry(TIMEOUT_MS, supplier, retriable);
|
||||
}
|
||||
|
||||
default <T> T retry(long timeoutMs, TemperamentalSupplier<T> supplier,
|
||||
Collection<Class<? extends Throwable>> retriable) throws Throwable {
|
||||
long retryAttempts = timeoutMs / RETRY_INTERVAL_MS;
|
||||
Throwable lastExc = null;
|
||||
for (int i = 0; i < retryAttempts; i++) {
|
||||
if (i != 0) {
|
||||
Thread.sleep(RETRY_INTERVAL_MS);
|
||||
}
|
||||
try {
|
||||
return supplier.get();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (i < 10) {
|
||||
Msg.debug(this, "Retrying after " + e);
|
||||
}
|
||||
lastExc = e;
|
||||
for (Class<? extends Throwable> et : retriable) {
|
||||
if (et.isAssignableFrom(e.getClass())) {
|
||||
e = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (e != null) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastExc;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.async.AsyncTimer;
|
||||
|
||||
public class AsyncTimerTest {
|
||||
@Test
|
||||
public void testMarkWait1000ms() throws Exception {
|
||||
|
|
|
@ -1,384 +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.async;
|
||||
|
||||
import static ghidra.async.AsyncUtils.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AsyncUtilsTest {
|
||||
@Test
|
||||
public void testEach() throws Throwable {
|
||||
List<Integer> list = Arrays.asList(new Integer[] { 1, 2, 4, 3 });
|
||||
List<String> res = new ArrayList<>();
|
||||
each(TypeSpec.VOID, list.iterator(), (e, seq) -> {
|
||||
append("" + e, res).handle(seq::repeat);
|
||||
}).get(1000, TimeUnit.MILLISECONDS);
|
||||
|
||||
List<String> exp = Arrays.asList(new String[] { "1", "2", "4", "3" });
|
||||
assertEquals(exp, res);
|
||||
}
|
||||
|
||||
// This also tests the compile-time type checking
|
||||
@Test
|
||||
public void testSeq() throws Throwable {
|
||||
List<String> res = new ArrayList<>();
|
||||
sequence(TypeSpec.VOID).then((seq) -> {
|
||||
add(1, 2).handle(seq::next);
|
||||
}, TypeSpec.INT).then((sum, seq) -> {
|
||||
intToString(sum).handle(seq::next);
|
||||
}, TypeSpec.STRING).then((str, seq) -> {
|
||||
append(str, res).handle(seq::next);
|
||||
}).finish().get(1000, TimeUnit.MILLISECONDS);
|
||||
|
||||
List<String> exp = Arrays.asList(new String[] { "3" });
|
||||
assertEquals(exp, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoop() throws Throwable {
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
List<Integer> res = new ArrayList<>();
|
||||
long result = loop(TypeSpec.LONG, (loop) -> {
|
||||
if (count.getAndIncrement() < 5) {
|
||||
add(count.get(), 10).handle(loop::consume);
|
||||
}
|
||||
else {
|
||||
loop.exit(0xdeadbeeff00dL, null);
|
||||
}
|
||||
}, TypeSpec.INT, (cur, loop) -> {
|
||||
res.add(cur);
|
||||
loop.repeat();
|
||||
}).get(1000, TimeUnit.MILLISECONDS);
|
||||
|
||||
List<Integer> exp = Arrays.asList(new Integer[] { 11, 12, 13, 14, 15 });
|
||||
assertEquals(exp, res);
|
||||
assertEquals(0xdeadbeeff00dL, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNesting() throws Throwable {
|
||||
List<String> res = new ArrayList<>();
|
||||
sequence(TypeSpec.VOID).then((seq) -> {
|
||||
getListInts().handle(seq::next);
|
||||
}, TypeSpec.obj((List<Integer>) null)).then((list, seq) -> {
|
||||
each(TypeSpec.VOID, list.iterator(), (e, loop) -> {
|
||||
intToString(e).handle(loop::consume);
|
||||
}, TypeSpec.STRING, (str, loop) -> {
|
||||
res.add(str);
|
||||
loop.repeat();
|
||||
}).handle(seq::next);
|
||||
}).finish().get(1000, TimeUnit.MILLISECONDS);
|
||||
|
||||
List<String> exp = Arrays.asList(new String[] { "1", "2", "3" });
|
||||
assertEquals(exp, res);
|
||||
}
|
||||
|
||||
// Functions that communicate result via completion handler
|
||||
protected static CompletableFuture<Void> append(String message, List<String> to) {
|
||||
to.add(message);
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
protected static CompletableFuture<Integer> add(int a, int b) {
|
||||
return CompletableFuture.completedFuture(a + b);
|
||||
}
|
||||
|
||||
protected static CompletableFuture<String> intToString(int a) {
|
||||
return CompletableFuture.completedFuture(Integer.toString(a));
|
||||
}
|
||||
|
||||
protected static CompletableFuture<List<Integer>> getListInts() {
|
||||
return CompletableFuture.completedFuture(Arrays.asList(new Integer[] { 1, 2, 3 }));
|
||||
}
|
||||
|
||||
// Some dummies to construct examples for documentation
|
||||
|
||||
protected static class Storage {
|
||||
protected CompletableFuture<List<Integer>> fetchList() {
|
||||
return getListInts();
|
||||
}
|
||||
|
||||
protected void close() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
protected static class Service {
|
||||
protected CompletableFuture<Integer> process(int val) {
|
||||
return CompletableFuture.completedFuture(val * 3);
|
||||
}
|
||||
|
||||
protected void close() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
|
||||
protected static CompletableFuture<Storage> connectStorage(String address) {
|
||||
return CompletableFuture.completedFuture(new Storage());
|
||||
}
|
||||
|
||||
protected static CompletableFuture<Service> connectService(String address) {
|
||||
return CompletableFuture.completedFuture(new Service());
|
||||
}
|
||||
|
||||
protected static String ADDR1 = null;
|
||||
protected static String ADDR2 = null;
|
||||
|
||||
class FetchAndProcess extends CompletableFuture<Integer> {
|
||||
Storage storage;
|
||||
Service service;
|
||||
int sum;
|
||||
|
||||
FetchAndProcess(int start) {
|
||||
sum = start;
|
||||
connectStorage(ADDR1).handle(this::storageConnected);
|
||||
}
|
||||
|
||||
Void storageConnected(Storage s, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
storage = s;
|
||||
connectService(ADDR2).handle(this::serviceConnected);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Void serviceConnected(Service s, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
service = s;
|
||||
storage.fetchList().handle(this::fetchedList);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Void fetchedList(List<Integer> list, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
for (int entry : list) {
|
||||
futures.add(service.process(entry).thenAccept((result) -> {
|
||||
sum += result;
|
||||
}));
|
||||
}
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]))
|
||||
.handle(this::processedList);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Void processedList(Void v, Throwable exc) {
|
||||
if (exc != null) {
|
||||
completeExceptionally(exc);
|
||||
}
|
||||
else {
|
||||
complete(sum);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> doWorkWithClass(int start) {
|
||||
return new FetchAndProcess(start);
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> doWorkWithComposition(int start) {
|
||||
AtomicReference<Storage> store = new AtomicReference<>();
|
||||
AtomicReference<Service> serve = new AtomicReference<>();
|
||||
AtomicInteger sum = new AtomicInteger(start);
|
||||
return connectStorage(ADDR1).thenCompose((s) -> {
|
||||
store.set(s);
|
||||
return connectService(ADDR2);
|
||||
}).thenCompose((s) -> {
|
||||
serve.set(s);
|
||||
return store.get().fetchList();
|
||||
}).thenCompose((list) -> {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
for (int entry : list) {
|
||||
futures.add(serve.get().process(entry).thenAccept((result) -> {
|
||||
sum.addAndGet(result);
|
||||
}));
|
||||
}
|
||||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]));
|
||||
}).thenApply((v) -> {
|
||||
store.get().close();
|
||||
serve.get().close();
|
||||
return sum.get();
|
||||
});
|
||||
}
|
||||
|
||||
static class ListNotFoundException extends Exception {
|
||||
// Just a placeholder for an example
|
||||
}
|
||||
|
||||
public static final List<Integer> DEFAULT_LIST = null;
|
||||
|
||||
public CompletableFuture<Integer> doWorkWithSeq(int start) {
|
||||
AtomicReference<Storage> store = new AtomicReference<>();
|
||||
AtomicReference<Service> serve = new AtomicReference<>();
|
||||
AtomicInteger sum = new AtomicInteger(start);
|
||||
return sequence(TypeSpec.INT).then((seq) -> {
|
||||
connectStorage(ADDR1).handle(seq::next);
|
||||
}, store).then((seq) -> {
|
||||
connectService(ADDR2).handle(seq::next);
|
||||
}, serve).then((seq) -> {
|
||||
store.get().fetchList().handle(seq::next);
|
||||
}, TypeSpec.obj((List<Integer>) null)).then((list, seq) -> {
|
||||
AsyncFence fence = new AsyncFence();
|
||||
for (int entry : list) {
|
||||
fence.include(sequence(TypeSpec.VOID).then((seq2) -> {
|
||||
serve.get().process(entry).handle(seq2::next);
|
||||
}, TypeSpec.INT).then((result, seq2) -> {
|
||||
sum.addAndGet(result);
|
||||
seq2.exit();
|
||||
}).finish());
|
||||
}
|
||||
fence.ready().handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
store.get().close();
|
||||
serve.get().close();
|
||||
seq.exit(sum.get());
|
||||
}).finish().exceptionally((exc) -> {
|
||||
if (store.get() != null) {
|
||||
store.get().close();
|
||||
}
|
||||
if (serve.get() != null) {
|
||||
serve.get().close();
|
||||
}
|
||||
return ExceptionUtils.rethrow(exc);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExample() throws Throwable {
|
||||
assertEquals(23, doWorkWithClass(5).get(1000, TimeUnit.MILLISECONDS).intValue());
|
||||
assertEquals(23, doWorkWithComposition(5).get(1000, TimeUnit.MILLISECONDS).intValue());
|
||||
assertEquals(23, doWorkWithSeq(5).get(1000, TimeUnit.MILLISECONDS).intValue());
|
||||
}
|
||||
|
||||
private CompletableFuture<byte[]> receiveData() {
|
||||
// Placeholder for example
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
private void processData(byte[] data) {
|
||||
// Placeholder for example
|
||||
}
|
||||
|
||||
public void exampleLoop1() {
|
||||
loop(TypeSpec.VOID, (loop) -> {
|
||||
receiveData().handle(loop::consume);
|
||||
}, TypeSpec.BYTE_ARRAY, (data, loop) -> {
|
||||
loop.repeat();
|
||||
processData(data);
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> someTask() {
|
||||
// Placeholder for example
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
public void exampleLoop2() {
|
||||
loop(TypeSpec.VOID, (loop) -> {
|
||||
someTask().handle(loop::repeat);
|
||||
});
|
||||
}
|
||||
|
||||
private Set<Integer> mySet;
|
||||
|
||||
private CompletableFuture<String> sendItem() {
|
||||
// Placeholder for example
|
||||
return AsyncUtils.nil();
|
||||
}
|
||||
|
||||
private void logResult(String message) {
|
||||
// Placeholder for example
|
||||
}
|
||||
|
||||
public void exampleEach1() {
|
||||
each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
|
||||
sendItem().handle(loop::consume);
|
||||
}, TypeSpec.STRING, (message, loop) -> {
|
||||
loop.repeat();
|
||||
logResult(message);
|
||||
});
|
||||
}
|
||||
|
||||
public void exampleEach2() {
|
||||
each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
|
||||
sendItem().handle(loop::repeatIgnore);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoSequencesInterwoven() {
|
||||
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
|
||||
List<Integer> result = new ArrayList<>();
|
||||
|
||||
sequence(TypeSpec.VOID).then((seq) -> {
|
||||
result.add(1);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(2);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(3);
|
||||
seq.exit();
|
||||
});
|
||||
sequence(TypeSpec.VOID).then((seq) -> {
|
||||
result.add(4);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(5);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
queue.add(future);
|
||||
future.handle(seq::next);
|
||||
}).then((seq) -> {
|
||||
result.add(6);
|
||||
seq.exit();
|
||||
});
|
||||
|
||||
CompletableFuture<Void> future;
|
||||
while (null != (future = queue.poll())) {
|
||||
future.complete(null);
|
||||
}
|
||||
|
||||
List<Integer> exp = Arrays.asList(new Integer[] { 1, 4, 2, 5, 3, 6 });
|
||||
assertEquals(exp, result);
|
||||
}
|
||||
}
|
|
@ -1,394 +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.comm.util;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
|
||||
import javax.tools.*;
|
||||
import javax.tools.JavaCompiler.CompilationTask;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class BitmaskSetTest {
|
||||
public enum TestUniverse implements BitmaskUniverse {
|
||||
FIRST(1 << 0), SECOND(1 << 1), THIRD(1 << 2), FOURTH(1 << 3);
|
||||
|
||||
TestUniverse(long mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
final long mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TestAlternate implements BitmaskUniverse {
|
||||
FIFTH(1 << 4), SIXTH(1 << 5), SEVENTH(1 << 6), EIGHTH(1 << 7);
|
||||
|
||||
TestAlternate(long mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
final long mask;
|
||||
|
||||
@Override
|
||||
public long getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
||||
Set<Integer> intOf0 = new HashSet<>();
|
||||
Set<Integer> intOf3 = new HashSet<>(Arrays.asList(new Integer[] { 0, 1, 2 }));
|
||||
Set<String> strOf0 = new HashSet<>();
|
||||
|
||||
BitmaskSet<TestUniverse> setOf0 = BitmaskSet.of();
|
||||
BitmaskSet<TestUniverse> setOf1 = BitmaskSet.of(TestUniverse.FIRST);
|
||||
BitmaskSet<TestUniverse> setOf2 = BitmaskSet.of(TestUniverse.FIRST, TestUniverse.SECOND);
|
||||
BitmaskSet<TestUniverse> setOf2a = BitmaskSet.of(TestUniverse.FIRST, TestUniverse.THIRD);
|
||||
BitmaskSet<TestUniverse> setOf3 =
|
||||
BitmaskSet.of(TestUniverse.FIRST, TestUniverse.SECOND, TestUniverse.THIRD);
|
||||
|
||||
BitmaskSet<TestAlternate> altOf0 = BitmaskSet.of();
|
||||
|
||||
@Test
|
||||
public void testEmptiesDifferentTypesEqual() {
|
||||
// This portion verifies the behavior of stock Java collections
|
||||
assertEquals(intOf0, strOf0);
|
||||
|
||||
// This portion verifies that BitmaskSet imitates that behavior
|
||||
assertEquals(setOf0, altOf0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOf() {
|
||||
assertEquals(TestUniverse.class, setOf1.getUniverse());
|
||||
assertEquals(TestUniverse.class, setOf0.getUniverse());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unlikely-arg-type") // Fair enough, Java. Fair enough. But it passes :)
|
||||
public void testContainsEmptyDifferentType() {
|
||||
// Check java behavior
|
||||
assertTrue(intOf3.containsAll(strOf0));
|
||||
// Check that BitmaskSet imitates it
|
||||
assertTrue(setOf2.containsAll(altOf0));
|
||||
assertTrue(setOf2.containsAll(strOf0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOfHasSafeVarargs() {
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter out = new PrintWriter(writer);
|
||||
|
||||
out.println("import " + Set.class.getCanonicalName() + ";");
|
||||
out.println("import " + BitmaskSet.class.getCanonicalName() + ";");
|
||||
out.println("import " + TestUniverse.class.getCanonicalName() + ";");
|
||||
out.println("import " + TestAlternate.class.getCanonicalName() + ";");
|
||||
out.println("");
|
||||
out.println("public class RequireFail {");
|
||||
out.println(" public static void main(String[] args) {");
|
||||
out.println(" Set<TestUniverse> testSet1 =");
|
||||
out.println(" BitmaskSet.of(TestUniverse.FIRST, TestAlternate.FIFTH);");
|
||||
out.println(" }");
|
||||
out.println("}");
|
||||
|
||||
out.close();
|
||||
JavaFileObject file =
|
||||
new SimpleJavaFileObject(URI.create("string:///RequireFail.java"), Kind.SOURCE) {
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors)
|
||||
throws IOException {
|
||||
return writer.toString();
|
||||
}
|
||||
};
|
||||
Collection<JavaFileObject> units = Collections.singleton(file);
|
||||
|
||||
JavaFileManager fmgr = new ForwardingJavaFileManager<StandardJavaFileManager>(
|
||||
compiler.getStandardFileManager(diagnostics, null, null)) {
|
||||
|
||||
@Override
|
||||
public FileObject getFileForOutput(Location location, String packageName,
|
||||
String relativeName, FileObject sibling) throws IOException {
|
||||
Msg.debug(this, "Got request for output: '" + relativeName + "'");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
CompilationTask task = compiler.getTask(null, fmgr, diagnostics,
|
||||
Arrays.asList("-classpath", System.getProperty("java.class.path")), null, units);
|
||||
assertFalse("Compilation should have failed", task.call());
|
||||
|
||||
String firstMessage = null;
|
||||
for (Diagnostic<? extends JavaFileObject> diag : diagnostics.getDiagnostics()) {
|
||||
if (firstMessage == null) {
|
||||
firstMessage = diag.getLineNumber() + ":" + diag.getColumnNumber() + ": " +
|
||||
diag.getMessage(null);
|
||||
}
|
||||
if (diag.getMessage(null).contains(
|
||||
"method of in class ghidra.comm.util.BitmaskSet<E> cannot be applied to given types")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("Unexpected compilation error, or no error: " + firstMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopy() {
|
||||
BitmaskSet<TestUniverse> test;
|
||||
|
||||
test = new BitmaskSet<>(setOf0);
|
||||
assertEquals(0, test.getBitmask());
|
||||
|
||||
test = new BitmaskSet<>(TestUniverse.class, setOf2);
|
||||
assertEquals(3, test.getBitmask());
|
||||
|
||||
test = new BitmaskSet<>(TestUniverse.class, new HashSet<>(setOf0));
|
||||
assertEquals(0, test.getBitmask());
|
||||
|
||||
test = new BitmaskSet<>(TestUniverse.class, new HashSet<>(setOf2));
|
||||
assertEquals(3, test.getBitmask());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
@Test
|
||||
public void testEquality() {
|
||||
assertFalse(setOf2.equals("Some string"));
|
||||
|
||||
assertTrue(setOf2.equals(setOf2));
|
||||
assertTrue(setOf2.equals(new HashSet<>(setOf2)));
|
||||
|
||||
assertFalse(setOf2.equals(setOf1));
|
||||
assertFalse(setOf2.equals(new HashSet<>(setOf1)));
|
||||
|
||||
assertFalse(setOf2.equals(setOf3));
|
||||
assertFalse(setOf2.equals(new HashSet<>(setOf3)));
|
||||
|
||||
assertEquals(setOf2.hashCode(),
|
||||
new BitmaskSet<>(TestUniverse.class, new HashSet<>(setOf2)).hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
assertTrue(setOf0.isEmpty());
|
||||
assertEquals(0, setOf0.size());
|
||||
assertEquals(1, setOf1.size());
|
||||
assertEquals(2, setOf2.size());
|
||||
|
||||
assertEquals(0, new BitmaskSet<>(TestUniverse.class).size());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
@Test
|
||||
public void testContains() {
|
||||
assertFalse(setOf0.contains(TestUniverse.FIRST));
|
||||
assertTrue(setOf1.contains(TestUniverse.FIRST));
|
||||
assertFalse(setOf1.contains(TestUniverse.SECOND));
|
||||
assertFalse(setOf1.contains("Some string"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator() {
|
||||
Set<TestUniverse> test;
|
||||
Set<TestUniverse> exp;
|
||||
|
||||
test = new HashSet<>(setOf2);
|
||||
exp = new HashSet<>(Arrays.asList(TestUniverse.FIRST, TestUniverse.SECOND));
|
||||
assertEquals(exp, test);
|
||||
|
||||
test = new HashSet<>(setOf0);
|
||||
exp = new HashSet<>();
|
||||
assertEquals(exp, test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArray() {
|
||||
TestUniverse[] arr;
|
||||
|
||||
arr = setOf0.toArray(new TestUniverse[] {});
|
||||
assertEquals(0, arr.length);
|
||||
|
||||
arr = setOf2.toArray(new TestUniverse[] {});
|
||||
assertEquals(2, arr.length);
|
||||
assertEquals(TestUniverse.FIRST, arr[0]);
|
||||
assertEquals(TestUniverse.SECOND, arr[1]);
|
||||
|
||||
Object[] oarr = setOf2.toArray();
|
||||
assertEquals(2, oarr.length);
|
||||
assertEquals(TestUniverse.FIRST, oarr[0]);
|
||||
assertEquals(TestUniverse.SECOND, oarr[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf1);
|
||||
assertTrue(test.add(TestUniverse.SECOND));
|
||||
assertEquals(setOf2, test);
|
||||
|
||||
assertFalse(test.add(TestUniverse.SECOND));
|
||||
assertEquals(setOf2, test);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unlikely-arg-type")
|
||||
@Test
|
||||
public void testRemove() {
|
||||
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.remove(TestUniverse.SECOND));
|
||||
assertEquals(setOf1, test);
|
||||
|
||||
assertFalse(test.remove(TestUniverse.SECOND));
|
||||
assertEquals(setOf1, test);
|
||||
|
||||
assertFalse(test.remove("Some string"));
|
||||
assertEquals(setOf1, test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainsAll() {
|
||||
assertTrue(setOf0.containsAll(setOf0));
|
||||
assertFalse(setOf0.containsAll(setOf1));
|
||||
assertTrue(setOf0.containsAll(new HashSet<>()));
|
||||
assertFalse(setOf0.containsAll(new HashSet<>(setOf1)));
|
||||
|
||||
assertTrue(setOf2.containsAll(setOf1));
|
||||
assertFalse(setOf1.containsAll(setOf2));
|
||||
assertTrue(setOf2.containsAll(new HashSet<>(setOf1)));
|
||||
assertFalse(setOf1.containsAll(new HashSet<>(setOf2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnion() {
|
||||
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.addAll(setOf2a));
|
||||
assertEquals(setOf3, test);
|
||||
assertFalse(test.addAll(setOf2a));
|
||||
assertEquals(setOf3, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.addAll(new HashSet<>(setOf2a)));
|
||||
assertEquals(setOf3, test);
|
||||
assertFalse(test.addAll(new HashSet<>(setOf2a)));
|
||||
assertEquals(setOf3, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf0);
|
||||
assertFalse(test.addAll(setOf0));
|
||||
assertEquals(setOf0, test);
|
||||
assertFalse(test.addAll(new HashSet<>(setOf0)));
|
||||
assertEquals(setOf0, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf0);
|
||||
assertTrue(test.addAll(setOf1));
|
||||
assertEquals(setOf1, test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntersection() {
|
||||
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.retainAll(setOf2a));
|
||||
assertEquals(setOf1, test);
|
||||
assertFalse(test.retainAll(setOf2a));
|
||||
assertEquals(setOf1, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.retainAll(new HashSet<>(setOf2a)));
|
||||
assertEquals(setOf1, test);
|
||||
assertFalse(test.retainAll(new HashSet<>(setOf2a)));
|
||||
assertEquals(setOf1, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.retainAll(new HashSet<>()));
|
||||
assertEquals(setOf0, test);
|
||||
assertFalse(test.retainAll(new HashSet<>()));
|
||||
assertEquals(setOf0, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf2);
|
||||
Set<Object> temp = new HashSet<>();
|
||||
temp.addAll(setOf2a);
|
||||
temp.add("Some string");
|
||||
assertTrue(test.retainAll(temp));
|
||||
assertEquals(setOf1, test);
|
||||
assertFalse(test.retainAll(temp));
|
||||
assertEquals(setOf1, test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubtraction() {
|
||||
BitmaskSet<TestUniverse> exp = BitmaskSet.of(TestUniverse.SECOND);
|
||||
|
||||
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.removeAll(setOf2a));
|
||||
assertEquals(exp, test);
|
||||
assertFalse(test.removeAll(setOf2a));
|
||||
assertEquals(exp, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf2);
|
||||
assertTrue(test.removeAll(new HashSet<>(setOf2a)));
|
||||
assertEquals(exp, test);
|
||||
assertFalse(test.removeAll(new HashSet<>(setOf2a)));
|
||||
assertEquals(exp, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf2);
|
||||
assertFalse(test.removeAll(new HashSet<>()));
|
||||
assertEquals(setOf2, test);
|
||||
|
||||
test = new BitmaskSet<>(setOf2);
|
||||
Set<Object> temp = new HashSet<>();
|
||||
temp.addAll(setOf2a);
|
||||
temp.add("Some string");
|
||||
assertTrue(test.removeAll(temp));
|
||||
assertEquals(exp, test);
|
||||
assertFalse(test.removeAll(temp));
|
||||
assertEquals(exp, test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() {
|
||||
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
|
||||
test.clear();
|
||||
assertEquals(setOf0, test);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
assertEquals("[]", setOf0.toString());
|
||||
assertEquals("[FIRST]", setOf1.toString());
|
||||
assertEquals("[FIRST, SECOND]", setOf2.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBitmask() {
|
||||
assertEquals(0, setOf0.getBitmask());
|
||||
assertEquals(1, setOf1.getBitmask());
|
||||
assertEquals(3, setOf2.getBitmask());
|
||||
assertEquals(7, setOf3.getBitmask());
|
||||
|
||||
BitmaskSet<TestUniverse> test = new BitmaskSet<>(TestUniverse.class);
|
||||
test.setBitmask(5);
|
||||
assertEquals(test, BitmaskSet.of(TestUniverse.FIRST, TestUniverse.THIRD));
|
||||
}
|
||||
}
|
|
@ -15,13 +15,15 @@
|
|||
*/
|
||||
package ghidra.program.model.address;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* A class to break a range of addresses into 'chunks' of a give size. This is useful to
|
||||
* break-up processing of large swaths of addresses, such as when performing work in a
|
||||
* background thread. Doing this allows the client to iterator over the range, pausing
|
||||
* enough to allow the UI to update.
|
||||
* A class to break a range of addresses into 'chunks' of a give size. This is useful to break-up
|
||||
* processing of large swaths of addresses, such as when performing work in a background thread.
|
||||
* Doing this allows the client to iterator over the range, pausing enough to allow the UI to
|
||||
* update.
|
||||
*/
|
||||
public class AddressRangeChunker implements Iterable<AddressRange> {
|
||||
|
||||
|
@ -106,4 +108,22 @@ public class AddressRangeChunker implements Iterable<AddressRange> {
|
|||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<AddressRange> spliterator() {
|
||||
long countAddrs = end.subtract(nextStartAddress) + 1;
|
||||
long size = Long.divideUnsigned(countAddrs + chunkSize - 1, chunkSize);
|
||||
return Spliterators.spliterator(iterator(), size,
|
||||
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
|
||||
Spliterator.SIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream the chunks
|
||||
*
|
||||
* @return the stream
|
||||
*/
|
||||
public Stream<AddressRange> stream() {
|
||||
return StreamSupport.stream(spliterator(), false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
package ghidra.program.model.address;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Defines a read-only interface for an address set.
|
||||
|
@ -33,13 +35,13 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Test if the given address range is contained in this set.
|
||||
* The specified start and end addresses must form a valid range within
|
||||
* a single {@link AddressSpace}.
|
||||
* <p>
|
||||
* The specified start and end addresses must form a valid range within a single
|
||||
* {@link AddressSpace}.
|
||||
*
|
||||
* @param start the first address in the range.
|
||||
* @param end the last address in the range.
|
||||
* @return true if entire range is contained within the set,
|
||||
* false otherwise.
|
||||
* @return true if entire range is contained within the set, false otherwise.
|
||||
*/
|
||||
public boolean contains(Address start, Address end);
|
||||
|
||||
|
@ -47,8 +49,7 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
* Test if the given address set is a subset of this set.
|
||||
*
|
||||
* @param rangeSet the set to test.
|
||||
* @return true if the entire set is contained within this set,
|
||||
* false otherwise.
|
||||
* @return true if the entire set is contained within this set, false otherwise.
|
||||
*/
|
||||
public boolean contains(AddressSetView rangeSet);
|
||||
|
||||
|
@ -59,8 +60,9 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Get the minimum address for this address set.
|
||||
* NOTE: An {@link AddressRange} should generally not be formed using this address
|
||||
* and {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s.
|
||||
* <p>
|
||||
* NOTE: An {@link AddressRange} should generally not be formed using this address and
|
||||
* {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s.
|
||||
*
|
||||
* @return the minimum address for this set. Returns null if the set is empty.
|
||||
*/
|
||||
|
@ -68,8 +70,9 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Get the maximum address for this address set.
|
||||
* NOTE: An {@link AddressRange} should generally not be formed using this address
|
||||
* and {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s.
|
||||
* <p>
|
||||
* NOTE: An {@link AddressRange} should generally not be formed using this address and
|
||||
* {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s.
|
||||
*
|
||||
* @return the maximum address for this set. Returns null if the set is empty.
|
||||
*/
|
||||
|
@ -87,17 +90,21 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Returns an iterator over the ranges in the specified order
|
||||
* @param forward the ranges are returned from lowest to highest, otherwise from
|
||||
* highest to lowest
|
||||
*
|
||||
* @param forward the ranges are returned from lowest to highest, otherwise from highest to
|
||||
* lowest
|
||||
* @return an iterator over all the addresse ranges in the set.
|
||||
*/
|
||||
public AddressRangeIterator getAddressRanges(boolean forward);
|
||||
|
||||
/**
|
||||
* Returns an iterator of address ranges starting with the range that contains the given address.
|
||||
* If there is no range containing the start address, then the first range will be
|
||||
* the next range greater than the start address if going forward, otherwise the range less than
|
||||
* the start address
|
||||
* Returns an iterator of address ranges starting with the range that contains the given
|
||||
* address.
|
||||
* <p>
|
||||
* If there is no range containing the start address, then the first range will be the next
|
||||
* range greater than the start address if going forward, otherwise the range less than the
|
||||
* start address
|
||||
*
|
||||
* @param start the address the first range should contain.
|
||||
* @param forward true iterators forward, false backwards
|
||||
* @return the AddressRange iterator
|
||||
|
@ -110,25 +117,91 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
@Override
|
||||
public Iterator<AddressRange> iterator();
|
||||
|
||||
@Override
|
||||
default Spliterator<AddressRange> spliterator() {
|
||||
return Spliterators.spliterator(iterator(), getNumAddressRanges(),
|
||||
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
|
||||
Spliterator.SIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream the ranges in this set
|
||||
*
|
||||
* @return the stream
|
||||
*/
|
||||
default Stream<AddressRange> stream() {
|
||||
return StreamSupport.stream(spliterator(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the ranges in the specified order
|
||||
* @param forward the ranges are returned from lowest to highest, otherwise from
|
||||
* highest to lowest
|
||||
* @return an iterator over all the addresse ranges in the set.
|
||||
*
|
||||
* @param forward the ranges are returned from lowest to highest, otherwise from highest to
|
||||
* lowest
|
||||
* @return an iterator over all the address ranges in the set.
|
||||
*/
|
||||
public Iterator<AddressRange> iterator(boolean forward);
|
||||
|
||||
/**
|
||||
* Returns an iterator of address ranges starting with the range that contains the given address.
|
||||
* If there is no range containing the start address, then the first range will be
|
||||
* the next range greater than the start address if going forward, otherwise the range less than
|
||||
* the start address
|
||||
* Create a spliterator over the ranges, as in {@link #iterator(boolean)}
|
||||
*
|
||||
* @param forward true to traverse lowest to highest, false for reverse
|
||||
* @return a spliterator over all the address ranges in the set.
|
||||
*/
|
||||
default Spliterator<AddressRange> spliterator(boolean forward) {
|
||||
return Spliterators.spliterator(iterator(forward), getNumAddressRanges(),
|
||||
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
|
||||
Spliterator.SIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream the ranges in the set forward or backward
|
||||
*
|
||||
* @param forward true to stream lowest to highest, false for reverse
|
||||
* @return a stream over all the address ranges in the set.
|
||||
*/
|
||||
default Stream<AddressRange> stream(boolean forward) {
|
||||
return StreamSupport.stream(spliterator(forward), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator of address ranges starting with the range that contains the given
|
||||
* address.
|
||||
* <p>
|
||||
* If there is no range containing the start address, then the first range will be the next
|
||||
* range greater than the start address if going forward, otherwise the range less than the
|
||||
* start address
|
||||
*
|
||||
* @param start the address that the first range should contain.
|
||||
* @param forward true iterators forward, false backwards
|
||||
* @return the AddressRange iterator
|
||||
*/
|
||||
public Iterator<AddressRange> iterator(Address start, boolean forward);
|
||||
|
||||
/**
|
||||
* Create a spliterator over the ranges, as in {@link #iterator(boolean)}
|
||||
*
|
||||
* @param start the address that the first range should contain.
|
||||
* @param forward true to traverse lowest to highest, false for reverse
|
||||
* @return a spliterator over the address ranges.
|
||||
*/
|
||||
default Spliterator<AddressRange> spliterator(Address start, boolean forward) {
|
||||
return Spliterators.spliterator(iterator(start, forward), getNumAddressRanges(),
|
||||
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
|
||||
Spliterator.SIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream the ranges in the set as in {@link #iterator(Address, boolean)}
|
||||
*
|
||||
* @param start the address that the first range should contain.
|
||||
* @param forward true to stream lowest to highest, false for reverse
|
||||
* @return a stream over the address ranges.
|
||||
*/
|
||||
default Stream<AddressRange> stream(Address start, boolean forward) {
|
||||
return StreamSupport.stream(spliterator(start, forward), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of addresses in this set.
|
||||
*/
|
||||
|
@ -136,19 +209,19 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Returns an iterator over all addresses in this set.
|
||||
* @param forward if true the address are return in increasing order, otherwise in
|
||||
* decreasing order.
|
||||
*
|
||||
* @param forward if true the address are return in increasing order, otherwise in decreasing
|
||||
* order.
|
||||
* @return an iterator over all addresses in this set.
|
||||
*/
|
||||
public AddressIterator getAddresses(boolean forward);
|
||||
|
||||
/**
|
||||
* Returns an iterator over the addresses in this address set
|
||||
* starting at the start address
|
||||
* Returns an iterator over the addresses in this address set starting at the start address
|
||||
*
|
||||
* @param start address to start iterating at in the address set
|
||||
* @param forward if true address are return from lowest to highest, else from highest to lowest
|
||||
* @return an iterator over the addresses in this address set
|
||||
* starting at the start address
|
||||
* @return an iterator over the addresses in this address set starting at the start address
|
||||
*/
|
||||
public AddressIterator getAddresses(Address start, boolean forward);
|
||||
|
||||
|
@ -162,8 +235,10 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Determine if the start and end range intersects with the specified address set.
|
||||
* The specified start and end addresses must form a valid range within
|
||||
* a single {@link AddressSpace}.
|
||||
* <p>
|
||||
* The specified start and end addresses must form a valid range within a single
|
||||
* {@link AddressSpace}.
|
||||
*
|
||||
* @param start start of range
|
||||
* @param end end of range
|
||||
* @return true if the given range intersects this address set.
|
||||
|
@ -172,75 +247,86 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Computes the intersection of this address set with the given address set.
|
||||
* <p>
|
||||
* This method does not modify this address set.
|
||||
*
|
||||
* @param view the address set to intersect with.
|
||||
* @return AddressSet a new address set that contains all addresses that are
|
||||
* contained in both this set and the given set.
|
||||
* @return AddressSet a new address set that contains all addresses that are contained in both
|
||||
* this set and the given set.
|
||||
*/
|
||||
public AddressSet intersect(AddressSetView view);
|
||||
|
||||
/**
|
||||
* Computes the intersection of this address set with the given address range.
|
||||
* This method does not modify this address set.
|
||||
* The specified start and end addresses must form a valid range within
|
||||
* a single {@link AddressSpace}.
|
||||
* <p>
|
||||
* This method does not modify this address set. The specified start and end addresses must form
|
||||
* a valid range within a single {@link AddressSpace}.
|
||||
*
|
||||
* @param start start of range
|
||||
* @param end end of range
|
||||
* @return AddressSet a new address set that contains all addresses that are
|
||||
* contained in both this set and the given range.
|
||||
* @return AddressSet a new address set that contains all addresses that are contained in both
|
||||
* this set and the given range.
|
||||
*/
|
||||
public AddressSet intersectRange(Address start, Address end);
|
||||
|
||||
/**
|
||||
* Computes the union of this address set with the given address set. This
|
||||
* method does not change this address set.
|
||||
* Computes the union of this address set with the given address set.
|
||||
* <p>
|
||||
* This method does not change this address set.
|
||||
*
|
||||
* @param addrSet The address set to be unioned with this address set.
|
||||
* @return AddressSet A new address set which contains all the addresses
|
||||
* from both this set and the given set.
|
||||
* @return AddressSet A new address set which contains all the addresses from both this set and
|
||||
* the given set.
|
||||
*/
|
||||
public AddressSet union(AddressSetView addrSet);
|
||||
|
||||
/**
|
||||
* Computes the difference of this address set with the given address set
|
||||
* (this - set). Note that this is not the same as (set - this). This
|
||||
* method does not change this address set.
|
||||
* Computes the difference of this address set with the given address set (this - set).
|
||||
* <p>
|
||||
* Note that this is not the same as (set - this). This method does not change this address set.
|
||||
*
|
||||
* @param addrSet the set to subtract from this set.
|
||||
* @return AddressSet a new address set which contains all the addresses
|
||||
* that are in this set, but not in the given set.
|
||||
* @return AddressSet a new address set which contains all the addresses that are in this set,
|
||||
* but not in the given set.
|
||||
*/
|
||||
public AddressSet subtract(AddressSetView addrSet);
|
||||
|
||||
/**
|
||||
* Computes the exclusive-or of this address set with the given set. This
|
||||
* method does not modify this address set.
|
||||
* Computes the exclusive-or of this address set with the given set.
|
||||
* <p>
|
||||
* This method does not modify this address set.
|
||||
*
|
||||
* @param addrSet address set to exclusive-or with.
|
||||
* @return AddressSet a new address set containing all addresses that are in
|
||||
* either this set or the given set, but not in both sets
|
||||
* @return AddressSet a new address set containing all addresses that are in either this set or
|
||||
* the given set, but not in both sets
|
||||
*/
|
||||
public AddressSet xor(AddressSetView addrSet);
|
||||
|
||||
/**
|
||||
* Returns true if the given address set contains the same set of addresses
|
||||
* as this set.
|
||||
* Returns true if the given address set contains the same set of addresses as this set.
|
||||
*
|
||||
* @param view the address set to compare.
|
||||
* @return true if the given set contains the same addresses as this set.
|
||||
*/
|
||||
public boolean hasSameAddresses(AddressSetView view);
|
||||
|
||||
/**
|
||||
* Returns the first range in this set or null if the set is empty;
|
||||
* @return the first range in this set or null if the set is empty;
|
||||
* Returns the first range in this set or null if the set is empty
|
||||
*
|
||||
* @return the first range in this set or null if the set is empty
|
||||
*/
|
||||
public AddressRange getFirstRange();
|
||||
|
||||
/**
|
||||
* Returns the last range in this set or null if the set is empty;
|
||||
* @return the last range in this set or null if the set is empty;
|
||||
* Returns the last range in this set or null if the set is empty
|
||||
*
|
||||
* @return the last range in this set or null if the set is empty
|
||||
*/
|
||||
public AddressRange getLastRange();
|
||||
|
||||
/**
|
||||
* Returns the range that contains the given address
|
||||
*
|
||||
* @param address the address for which to find a range.
|
||||
* @return the range that contains the given address.
|
||||
*/
|
||||
|
@ -248,6 +334,7 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Finds the first address in this collection that is also in the given addressSet.
|
||||
*
|
||||
* @param set the addressSet to search for the first (lowest) common address.
|
||||
* @return the first address that is contained in this set and the given set.
|
||||
*/
|
||||
|
@ -255,6 +342,7 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
|
||||
/**
|
||||
* Returns the number of address in this address set before the given address.
|
||||
*
|
||||
* @param address the address after the last address to be counted
|
||||
* @return the number of address in this address set before the given address
|
||||
*/
|
||||
|
@ -274,10 +362,11 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Trim address set removing all addresses less-than-or-equal to specified
|
||||
* address based upon {@link Address} comparison.
|
||||
* The address set may contain address ranges from multiple
|
||||
* address spaces.
|
||||
* Trim address set removing all addresses less-than-or-equal to specified address based upon
|
||||
* {@link Address} comparison.
|
||||
* <p>
|
||||
* The address set may contain address ranges from multiple address spaces.
|
||||
*
|
||||
* @param set address set to be trimmed
|
||||
* @param addr trim point. Only addresses greater than this address will be returned.
|
||||
* @return trimmed address set view
|
||||
|
@ -301,10 +390,11 @@ public interface AddressSetView extends Iterable<AddressRange> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Trim address set removing all addresses greater-than-or-equal to specified
|
||||
* address based upon {@link Address} comparison.
|
||||
* The address set may contain address ranges from multiple
|
||||
* address spaces.
|
||||
* Trim address set removing all addresses greater-than-or-equal to specified address based upon
|
||||
* {@link Address} comparison.
|
||||
* <p>
|
||||
* The address set may contain address ranges from multiple address spaces.
|
||||
*
|
||||
* @param set address set to be trimmed
|
||||
* @param addr trim point. Only addresses less than this address will be returned.
|
||||
* @return trimmed address set view
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue