Last active
July 9, 2019 17:47
-
-
Save JohnnyJayJay/a84ef8202dee5e8e939159f5537d043b to your computer and use it in GitHub Desktop.
modified event waiter from jda-utilities for jda 4 alpha
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import net.dv8tion.jda.api.events.GenericEvent; | |
import net.dv8tion.jda.api.hooks.EventListener; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.ScheduledExecutorService; | |
import java.util.concurrent.TimeUnit; | |
import java.util.function.Consumer; | |
import java.util.function.Predicate; | |
/** | |
* This is an adaptation of jagrosh's EventWaiter from jda utilities | |
* (https://github.com/JDA-Applications/JDA-Utilities) for JDA 4 Alpha. | |
* Some changes have been made, but most of this code remained unchanged. | |
* | |
* <p>The EventExpecter is capable of handling specialized forms of | |
* {@link net.dv8tion.jda.api.events.GenericEvent GenericEvent} that must meet criteria not normally specifiable | |
* without implementation of an {@link net.dv8tion.jda.api.hooks.EventListener EventListener}. | |
* | |
* <p>Creating an EventExpecter requires provision and/or creation of a | |
* {@link java.util.concurrent.ScheduledExecutorService Executor}, and thus a proper | |
* shutdown of said executor. | |
* | |
* <p>For instances of this class to work, you need to register them as EventListeners to JDA. | |
* | |
* <p>As a final note, if you intend to use the EventExpecter, it is highly recommended you <b>DO NOT</b> | |
* create multiple EventExpecters! Doing this will cause unnecessary increases in memory usage. | |
* | |
* | |
* @author Johnny_JayJay / John Grosh (jagrosh) | |
*/ | |
public class EventExpecter implements EventListener { | |
private final Map<Class<?>, Set<ExpectedEvent>> expectedEvents; | |
private final ScheduledExecutorService threadpool; | |
/** | |
* Constructs an empty EventExpecter. | |
*/ | |
public EventExpecter() { | |
this(Executors.newSingleThreadScheduledExecutor()); | |
} | |
/** | |
* Constructs an EventExpecter using the provided {@link java.util.concurrent.ScheduledExecutorService Executor} | |
* as it's threadpool. | |
* | |
* <p>A developer might choose to use this constructor over the {@link EventExpecter#EventExpecter() default}, | |
* for using a alternate form of threadpool, as opposed to a {@link java.util.concurrent.Executors#newSingleThreadExecutor() single thread executor}. | |
* <p> | |
* It's worth noting that this EventExpecter can serve as a delegate to invoke the threadpool's shutdown via | |
* a call to {@link EventExpecter#shutdown()}. | |
* | |
* @param threadpool The ScheduledExecutorService to use for this EventExpecter's threadpool. | |
* @throws java.lang.IllegalArgumentException If the threadpool provided is {@code null} or | |
* {@link java.util.concurrent.ScheduledExecutorService#isShutdown() is shutdown} | |
* @see EventExpecter#shutdown() | |
*/ | |
public EventExpecter(ScheduledExecutorService threadpool) { | |
checkNotNull(threadpool, "ScheduledExecutorService"); | |
check(!threadpool.isShutdown(), "Cannot construct EventWaiter with a closed ScheduledExecutorService!"); | |
this.expectedEvents = new HashMap<>(); | |
this.threadpool = threadpool; | |
} | |
/** | |
* Gets whether the EventExpecter's internal ScheduledExecutorService | |
* {@link java.util.concurrent.ScheduledExecutorService#isShutdown() is shutdown}. | |
* | |
* @return {@code true} if the ScheduledExecutorService is shutdown, {@code false} otherwise. | |
*/ | |
public boolean isShutdown() { | |
return threadpool.isShutdown(); | |
} | |
/** | |
* Returns an instance of the builder-like class {@link ExpectationBuilder} to set up an event expectation conveniently. | |
* | |
* @param eventType The {@link java.lang.Class} of the Event to wait for. | |
* @param <T> The type of Event to wait for. | |
* @return A new instance of {@link ExpectationBuilder}. | |
*/ | |
public <T extends GenericEvent> ExpectationBuilder<T> expect(Class<T> eventType) { | |
return new ExpectationBuilder<>(eventType); | |
} | |
/** | |
* Waits (not blocking) an indefinite amount of time for an {@link org.bukkit.event.Event} that | |
* returns {@code true} when tested with the provided {@link java.util.function.Predicate Predicate}. | |
* | |
* <p>When this occurs, the provided {@link java.util.function.Consumer Consumer} will accept and | |
* execute using the same Event. | |
* | |
* @param <T> The type of Event to wait for. | |
* @param eventType The {@link java.lang.Class} of the Event to wait for. Never null. | |
* @param condition The Predicate to test when Events of the provided type are thrown. Never null. | |
* @param action The Consumer to perform an action when the condition Predicate returns {@code true}. Never null. | |
* @throws IllegalArgumentException One of two reasons: | |
* <ul> | |
* <li>1) Either the {@code eventType}, {@code condition}, or {@code action} was {@code null}.</li> | |
* <li>2) The internal threadpool is shut down, meaning that no more tasks can be submitted.</li> | |
* </ul> | |
*/ | |
public <T extends GenericEvent> void expect(Class<T> eventType, Predicate<T> condition, Consumer<T> action) { | |
expect(eventType, condition, action, -1, null, null); | |
} | |
/** | |
* Waits (not blocking) a predetermined amount of time for an {@link org.bukkit.event.Event} that | |
* returns {@code true} when tested with the provided {@link java.util.function.Predicate Predicate}. | |
* | |
* <p>Once started, there are two possible outcomes: | |
* <ul> | |
* <li>The correct Event occurs within the time allotted, and the provided | |
* {@link java.util.function.Consumer Consumer} will accept and execute using the same Event.</li> | |
* | |
* <li>The time limit is elapsed and the provided {@link java.lang.Runnable} is executed.</li> | |
* </ul> | |
* | |
* @param <T> The type of Event to wait for. | |
* @param eventType The {@link java.lang.Class} of the Event to wait for. Never null. | |
* @param condition The Predicate to test when Events of the provided type are thrown. Never null. | |
* @param action The Consumer to perform an action when the condition Predicate returns {@code true}. Never null. | |
* @param timeout The maximum amount of time to wait for, or {@code -1} if there is no timeout. | |
* @param unit The {@link java.util.concurrent.TimeUnit TimeUnit} measurement of the timeout, or | |
* {@code null} if there is no timeout. | |
* @param timeoutAction The Runnable to run if the time runs out before a correct Event is thrown, or | |
* {@code null} if there is no action on timeout. | |
* @throws IllegalArgumentException One of two reasons: | |
* <ul> | |
* <li>1) Either the {@code eventType}, {@code condition}, or {@code action} was {@code null}.</li> | |
* <li>2) The internal threadpool is shut down, meaning that no more tasks can be submitted.</li> | |
* </ul> | |
*/ | |
public <T extends GenericEvent> void expect(Class<T> eventType, Predicate<T> condition, Consumer<T> action, | |
long timeout, TimeUnit unit, Runnable timeoutAction) { | |
check(!isShutdown(), "Attempted to register an ExpectedEvent while the EventExpecter's threadpool was already shut down!"); | |
checkNotNull(eventType, "The provided class type"); | |
checkNotNull(condition, "The provided condition predicate"); | |
checkNotNull(action, "The provided action consumer"); | |
ExpectedEvent we = new ExpectedEvent<>(condition, action); | |
Set<ExpectedEvent> set = expectedEvents.computeIfAbsent(eventType, c -> new HashSet<>()); | |
set.add(we); | |
if (timeout > 0 && unit != null) { | |
threadpool.schedule(() -> | |
{ | |
if (set.remove(we) && timeoutAction != null) | |
timeoutAction.run(); | |
}, timeout, unit); | |
} | |
} | |
@SuppressWarnings("unchecked") | |
public final void onEvent(GenericEvent event) { | |
Class c = event.getClass(); | |
// Runs at least once for the fired Event, at most | |
// once for each superclass (excluding Object) because | |
// Class#getSuperclass() returns null when the superclass | |
// is primitive, void, or (in this case) Object. | |
while (c != null) { | |
if (expectedEvents.containsKey(c)) { | |
Set<ExpectedEvent> set = expectedEvents.get(c); | |
set.removeAll(set.stream().filter((expectedEvent) -> expectedEvent.attempt(event)).collect(Collectors.toSet())); | |
} | |
c = c.getSuperclass(); | |
} | |
} | |
/** | |
* Closes this EventExpecter. | |
*/ | |
public void shutdown() { | |
threadpool.shutdown(); | |
} | |
private class ExpectedEvent<T extends GenericEvent> { | |
final Predicate<T> condition; | |
final Consumer<T> action; | |
ExpectedEvent(Predicate<T> condition, Consumer<T> action) { | |
this.condition = condition; | |
this.action = action; | |
} | |
boolean attempt(T event) { | |
if (condition.test(event)) { | |
action.accept(event); | |
return true; | |
} | |
return false; | |
} | |
} | |
/** | |
* Class that is mainly used for chaining convenience when calling {@link #expect(Class)}. | |
* | |
* @param <T> The type of the event to expect. | |
* @see #expect(Class, Predicate, Consumer, long, TimeUnit, Runnable) | |
* | |
*/ | |
public class ExpectationBuilder<T extends GenericEvent> { | |
private final Class<T> eventType; | |
private Predicate<T> condition = (e) -> true; | |
private Consumer<T> action = (e) -> { | |
}; | |
private long timeout = -1; | |
private TimeUnit timeoutUnit = null; | |
private Runnable timeoutAction = null; | |
private ExpectationBuilder(Class<T> eventType) { | |
this.eventType = eventType; | |
} | |
/** | |
* Sets the condition for this expectation. If the Predicate returns true for a corresponding event, | |
* the Consumer set in {@link #thenAccept(Consumer)} will be called and the Expectation will be completed. | |
* | |
* @param condition The Predicate to test when Events of the provided type are thrown. | |
* @return this. | |
*/ | |
public ExpectationBuilder<T> takeIf(Predicate<T> condition) { | |
this.condition = condition; | |
return this; | |
} | |
/** | |
* Sets the Consumer that will be called if the right event happens. | |
* | |
* @param action The Consumer to perform an action when the condition Predicate returns {@code true}. | |
* @return this. | |
*/ | |
public ExpectationBuilder<T> thenAccept(Consumer<T> action) { | |
this.action = action; | |
return this; | |
} | |
/** | |
* Sets a timeout for the Expectation, i.e. after the the specified amount of time, the Expecter | |
* will stop waiting for the event. By default, there is no timeout. | |
* | |
* @param amount The maximum amount of time to wait for, or {@code -1} if there is no timeout. | |
* @param unit The {@link java.util.concurrent.TimeUnit TimeUnit} measurement of the timeout, or | |
* {@code null} if there is no timeout. | |
* @return this. | |
*/ | |
public ExpectationBuilder<T> timeoutAfter(long amount, TimeUnit unit) { | |
this.timeout = amount; | |
this.timeoutUnit = unit; | |
return this; | |
} | |
/** | |
* Sets the action to perform if the Expectation times out. | |
* | |
* @param action The Runnable to run if the time runs out before a correct Event is thrown, or | |
* {@code null} if there is no action on timeout. | |
* @return this. | |
*/ | |
public ExpectationBuilder<T> runOnTimeout(Runnable action) { | |
this.timeoutAction = action; | |
return this; | |
} | |
/** | |
* Finishes the Expectation setup and tells the EventExpecter to wait for the event (not blocking). | |
* This method simply calls {@link #expect(Class, Predicate, Consumer, long, TimeUnit, Runnable)}, | |
* so it behaves exactly the same way. | |
*/ | |
public void register() { | |
EventExpecter.this.expect(eventType, condition, action, timeout, timeoutUnit, timeoutAction); | |
} | |
} | |
private static void check(boolean condition, String message) { | |
if (!condition) | |
throw new IllegalArgumentException(message); | |
} | |
private static void checkNotNull(Object argument, String name) { | |
check(argument != null, name + " may not be null"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment