2021-06-02 08:17:21 +02:00
|
|
|
package net.minestom.server.event;
|
|
|
|
|
2021-06-09 14:05:37 +02:00
|
|
|
import net.minestom.server.MinecraftServer;
|
2021-06-09 07:11:01 +02:00
|
|
|
import net.minestom.server.event.trait.CancellableEvent;
|
2021-06-03 21:39:38 +02:00
|
|
|
import net.minestom.server.tag.Tag;
|
|
|
|
import net.minestom.server.tag.TagReadable;
|
2021-06-03 09:47:48 +02:00
|
|
|
import net.minestom.server.utils.validate.Check;
|
2021-06-08 14:15:30 +02:00
|
|
|
import org.jetbrains.annotations.Contract;
|
2021-06-02 10:19:23 +02:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
2021-06-03 21:39:38 +02:00
|
|
|
import org.jetbrains.annotations.Nullable;
|
2021-06-02 08:17:21 +02:00
|
|
|
|
2021-06-03 09:47:48 +02:00
|
|
|
import java.util.*;
|
2021-06-03 03:27:30 +02:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
2021-06-06 07:28:07 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
2021-06-02 21:08:10 +02:00
|
|
|
import java.util.function.BiPredicate;
|
2021-06-03 20:46:11 +02:00
|
|
|
import java.util.function.Consumer;
|
2021-06-02 08:17:21 +02:00
|
|
|
import java.util.function.Predicate;
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Represents a single node in an event graph.
|
|
|
|
* <p>
|
|
|
|
* A node may contain any number of children and/or listeners. When an event is called,
|
|
|
|
* the node will filter it based on the parameters given at creation and then propagate
|
|
|
|
* it down to child nodes and listeners if it passes.
|
|
|
|
*
|
|
|
|
* @param <T> The event type accepted by this node
|
|
|
|
*/
|
2021-06-03 04:26:47 +02:00
|
|
|
public class EventNode<T extends Event> {
|
2021-06-02 08:17:21 +02:00
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Creates an event node which accepts any event type with no filtering.
|
|
|
|
*
|
|
|
|
* @param name The name of the node
|
|
|
|
* @return An event node with no filtering
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_ -> new", pure = true)
|
|
|
|
public static @NotNull EventNode<Event> all(@NotNull String name) {
|
2021-06-04 04:10:13 +02:00
|
|
|
return type(name, EventFilter.ALL);
|
2021-06-02 11:29:16 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Creates an event node which accepts any event of the given type. The type is provided
|
|
|
|
* by the {@link EventFilter}.
|
|
|
|
* <p>
|
|
|
|
* For example, you could create an event filter which only accepts player events with the following
|
|
|
|
* <p><pre>
|
|
|
|
* var playerEventNode = EventNode.type("demo", EventFilter.PLAYER);
|
|
|
|
* </pre>
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The name of the event node
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param filter The event type filter to apply
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param <E> The resulting event type of the node
|
2021-06-08 22:58:48 +02:00
|
|
|
* @return A node with just an event type filter
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_, _ -> new", pure = true)
|
|
|
|
public static <E extends Event, V> @NotNull EventNode<E> type(@NotNull String name,
|
|
|
|
@NotNull EventFilter<E, V> filter) {
|
2021-06-04 04:39:45 +02:00
|
|
|
return create(name, filter, null);
|
2021-06-03 21:59:27 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Creates an event node which accepts any event of the given type which passes
|
|
|
|
* the provided condition. The condition is based on the event object itself.
|
|
|
|
* <p>
|
|
|
|
* For example, you could create an event filter which only accepts player events
|
|
|
|
* where the player is in the pos x/z quadrant of the world.
|
2021-06-10 14:41:44 +02:00
|
|
|
* <p><pre>{@code
|
2021-06-08 22:58:48 +02:00
|
|
|
* var playerInPosXZNode = EventNode.event("abc", EventFilter.PLAYER, event -> {
|
|
|
|
* var position = event.getPlayer().getPosition();
|
|
|
|
* return position.getX() > 0 && position.getZ() > 0;
|
|
|
|
* });
|
2021-06-10 14:41:44 +02:00
|
|
|
* }</pre>
|
2021-06-08 22:58:48 +02:00
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The name of the event node
|
|
|
|
* @param filter The event type filter to apply
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param predicate The event condition
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param <E> The resulting event type of the node
|
2021-06-08 22:58:48 +02:00
|
|
|
* @return A node with an event type filter as well as a condition on the event.
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_, _, _ -> new", pure = true)
|
|
|
|
public static <E extends Event, V> @NotNull EventNode<E> event(@NotNull String name,
|
|
|
|
@NotNull EventFilter<E, V> filter,
|
|
|
|
@NotNull Predicate<E> predicate) {
|
2021-06-04 04:39:45 +02:00
|
|
|
return create(name, filter, (e, h) -> predicate.test(e));
|
2021-06-02 21:08:10 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Creates an event node which accepts any event of the given type which passes
|
|
|
|
* the provided condition. The condition is based on the event object as well as
|
|
|
|
* the event handler type defined in the {@link EventFilter}.
|
|
|
|
* <p>
|
|
|
|
* For example, you could create an event filter which only accepts player events
|
|
|
|
* where the player is in the pos x/z quadrant of the world.
|
2021-06-10 14:41:44 +02:00
|
|
|
* <p><pre>{@code
|
2021-06-08 22:58:48 +02:00
|
|
|
* var playerInPosXZNode = EventNode.type("abc", EventFilter.PLAYER, (event, player) -> {
|
|
|
|
* var position = player.getPosition();
|
|
|
|
* return position.getX() > 0 && position.getZ() > 0;
|
|
|
|
* });
|
2021-06-10 14:41:44 +02:00
|
|
|
* }</pre>
|
2021-06-08 22:58:48 +02:00
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The name of the event node
|
|
|
|
* @param filter The event type filter to apply
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param predicate The event condition
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param <E> The resulting event type of the node
|
|
|
|
* @param <V> The handler type of the event filter
|
2021-06-08 22:58:48 +02:00
|
|
|
* @return A node with an event type filter as well as a condition on the event.
|
|
|
|
*/
|
|
|
|
@Contract(value = "_, _, _ -> new", pure = true)
|
|
|
|
public static <E extends Event, V> @NotNull EventNode<E> type(@NotNull String name,
|
|
|
|
@NotNull EventFilter<E, V> filter,
|
|
|
|
@NotNull BiPredicate<E, V> predicate) {
|
|
|
|
return create(name, filter, predicate);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an event node which accepts any event of the given type which passes
|
|
|
|
* the provided condition. The condition is based on the event handler defined
|
|
|
|
* by the {@link EventFilter}.
|
|
|
|
* <p>
|
|
|
|
* For example, you could create an event filter which only accepts player events
|
|
|
|
* where the player is in creative mode.
|
|
|
|
* <p><pre>
|
|
|
|
* var playerIsCreative = EventNode.value("abc", EventFilter.PLAYER, Player::isCreative);
|
|
|
|
* </pre>
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The name of the event node
|
|
|
|
* @param filter The event type filter to apply
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param predicate The event condition
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param <E> The resulting event type of the node
|
|
|
|
* @param <V> The handler type of the event filter
|
2021-06-08 22:58:48 +02:00
|
|
|
* @return A node with an event type filter as well as a condition on the event.
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_, _, _ -> new", pure = true)
|
|
|
|
public static <E extends Event, V> @NotNull EventNode<E> value(@NotNull String name,
|
|
|
|
@NotNull EventFilter<E, V> filter,
|
|
|
|
@NotNull Predicate<V> predicate) {
|
2021-06-04 04:39:45 +02:00
|
|
|
return create(name, filter, (e, h) -> predicate.test(h));
|
2021-06-02 21:08:10 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Creates an event node which accepts any event of the given type which has a handler who
|
|
|
|
* has the given tag.
|
2021-06-09 06:52:00 +02:00
|
|
|
* <p>
|
2021-06-08 22:58:48 +02:00
|
|
|
* The {@link EventFilter}'s resulting event type must be {@link TagReadable}.
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The name of the event node
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param filter The event type filter to apply
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param tag The tag which must be contained on the event handler
|
|
|
|
* @param <E> The resulting event type of the node
|
2021-06-08 22:58:48 +02:00
|
|
|
* @return A node with an event type filter as well as a handler with the provided tag
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_, _, _ -> new", pure = true)
|
|
|
|
public static <E extends Event> @NotNull EventNode<E> tag(@NotNull String name,
|
|
|
|
@NotNull EventFilter<E, ? extends TagReadable> filter,
|
|
|
|
@NotNull Tag<?> tag) {
|
2021-06-04 04:39:45 +02:00
|
|
|
return create(name, filter, (e, h) -> h.hasTag(tag));
|
2021-06-03 21:39:38 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Creates an event node which accepts any event of the given type which has a handler who
|
|
|
|
* has an applicable tag. An applicable tag means that it passes the given condition.
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The name of the event node
|
|
|
|
* @param filter The event type filter to apply
|
|
|
|
* @param tag The tag which must be contained on the event handler
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param consumer The condition to test against the tag, if it exists.
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param <E> The resulting event type of the node
|
2021-06-08 22:58:48 +02:00
|
|
|
* @return A node with an event type filter as well as a handler with the provided tag
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_, _, _, _ -> new", pure = true)
|
|
|
|
public static <E extends Event, V> @NotNull EventNode<E> tag(@NotNull String name,
|
|
|
|
@NotNull EventFilter<E, ? extends TagReadable> filter,
|
|
|
|
@NotNull Tag<V> tag,
|
|
|
|
@NotNull Predicate<@Nullable V> consumer) {
|
2021-06-04 04:39:45 +02:00
|
|
|
return create(name, filter, (e, h) -> consumer.test(h.getTag(tag)));
|
|
|
|
}
|
|
|
|
|
|
|
|
private static <E extends Event, V> EventNode<E> create(@NotNull String name,
|
|
|
|
@NotNull EventFilter<E, V> filter,
|
|
|
|
@Nullable BiPredicate<E, V> predicate) {
|
|
|
|
return new EventNode<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null);
|
2021-06-03 21:39:38 +02:00
|
|
|
}
|
|
|
|
|
2021-06-06 07:28:07 +02:00
|
|
|
private static final Object GLOBAL_CHILD_LOCK = new Object();
|
|
|
|
private final Object lock = new Object();
|
|
|
|
|
|
|
|
private final Map<Class<? extends T>, ListenerEntry<T>> listenerMap = new ConcurrentHashMap<>();
|
2021-06-03 04:26:47 +02:00
|
|
|
private final Set<EventNode<T>> children = new CopyOnWriteArraySet<>();
|
2021-06-02 10:19:23 +02:00
|
|
|
|
2021-06-04 04:10:13 +02:00
|
|
|
protected final String name;
|
2021-06-03 04:26:47 +02:00
|
|
|
protected final EventFilter<T, ?> filter;
|
|
|
|
protected final BiPredicate<T, Object> predicate;
|
2021-06-04 03:48:51 +02:00
|
|
|
protected final Class<T> eventType;
|
2021-06-08 13:47:10 +02:00
|
|
|
private volatile int priority;
|
2021-06-03 04:26:47 +02:00
|
|
|
private volatile EventNode<? super T> parent;
|
2021-06-02 10:19:23 +02:00
|
|
|
|
2021-06-04 04:39:45 +02:00
|
|
|
protected EventNode(@NotNull String name,
|
|
|
|
@NotNull EventFilter<T, ?> filter,
|
|
|
|
@Nullable BiPredicate<T, Object> predicate) {
|
2021-06-04 04:10:13 +02:00
|
|
|
this.name = name;
|
2021-06-03 03:27:30 +02:00
|
|
|
this.filter = filter;
|
2021-06-03 03:56:05 +02:00
|
|
|
this.predicate = predicate;
|
2021-06-04 03:48:51 +02:00
|
|
|
this.eventType = filter.getEventType();
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
2021-06-02 19:50:23 +02:00
|
|
|
|
2021-06-03 03:27:30 +02:00
|
|
|
/**
|
|
|
|
* Condition to enter the node.
|
|
|
|
*
|
|
|
|
* @param event the called event
|
|
|
|
* @return true to enter the node, false otherwise
|
|
|
|
*/
|
|
|
|
protected boolean condition(@NotNull T event) {
|
2021-06-04 04:39:45 +02:00
|
|
|
if (predicate == null)
|
|
|
|
return true;
|
2021-06-03 04:26:47 +02:00
|
|
|
final var value = filter.getHandler(event);
|
2021-06-14 14:42:10 +02:00
|
|
|
try {
|
|
|
|
return predicate.test(event, value);
|
|
|
|
} catch (Exception e) {
|
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
|
|
|
return false;
|
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
2021-06-03 00:48:37 +02:00
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Executes the given event on this node. The event must pass all conditions before
|
|
|
|
* it will be forwarded to the listeners.
|
|
|
|
* <p>
|
|
|
|
* Calling an event on a node will execute all child nodes, however, an event may be
|
|
|
|
* called anywhere on the event graph and it will propagate down from there only.
|
|
|
|
*
|
|
|
|
* @param event the event to execute
|
|
|
|
*/
|
2021-06-03 03:27:30 +02:00
|
|
|
public void call(@NotNull T event) {
|
2021-06-08 17:18:00 +02:00
|
|
|
final var eventClass = event.getClass();
|
|
|
|
if (!eventType.isAssignableFrom(eventClass)) {
|
2021-06-03 03:27:30 +02:00
|
|
|
// Invalid event type
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!condition(event)) {
|
|
|
|
// Cancelled by superclass
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Process listener list
|
2021-06-08 17:18:00 +02:00
|
|
|
final var entry = listenerMap.get(eventClass);
|
2021-06-06 07:28:07 +02:00
|
|
|
if (entry == null) {
|
|
|
|
// No listener nor children
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final var listeners = entry.listeners;
|
2021-06-06 07:55:30 +02:00
|
|
|
if (!listeners.isEmpty()) {
|
2021-06-06 07:34:33 +02:00
|
|
|
for (EventListener<T> listener : listeners) {
|
2021-06-09 14:05:37 +02:00
|
|
|
EventListener.Result result;
|
|
|
|
try {
|
|
|
|
result = listener.run(event);
|
|
|
|
} catch (Exception e) {
|
|
|
|
result = EventListener.Result.EXCEPTION;
|
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
if (result == EventListener.Result.EXPIRED) {
|
|
|
|
listeners.remove(listener);
|
|
|
|
}
|
2021-06-06 07:34:33 +02:00
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
// Process children
|
2021-06-06 07:28:07 +02:00
|
|
|
if (entry.childCount > 0) {
|
2021-06-08 13:47:10 +02:00
|
|
|
this.children.stream()
|
|
|
|
.sorted(Comparator.comparing(EventNode::getPriority))
|
|
|
|
.forEach(child -> child.call(event));
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-03 00:48:37 +02:00
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Execute a cancellable event with a callback to execute if the event is successful.
|
|
|
|
* Event conditions and propagation is the same as {@link #call(Event)}.
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param event The event to execute
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param successCallback A callback if the event is not cancelled
|
|
|
|
*/
|
2021-06-04 03:48:51 +02:00
|
|
|
public void callCancellable(@NotNull T event, @NotNull Runnable successCallback) {
|
|
|
|
call(event);
|
|
|
|
if (!(event instanceof CancellableEvent) || !((CancellableEvent) event).isCancelled()) {
|
|
|
|
successCallback.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(pure = true)
|
2021-06-06 07:28:07 +02:00
|
|
|
public @NotNull String getName() {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(pure = true)
|
2021-06-08 13:47:10 +02:00
|
|
|
public int getPriority() {
|
|
|
|
return priority;
|
|
|
|
}
|
|
|
|
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_ -> this")
|
|
|
|
public @NotNull EventNode<T> setPriority(int priority) {
|
2021-06-08 13:47:10 +02:00
|
|
|
this.priority = priority;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(pure = true)
|
2021-06-04 03:48:51 +02:00
|
|
|
public @Nullable EventNode<? super T> getParent() {
|
|
|
|
return parent;
|
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Returns an unmodifiable view of the children in this node.
|
|
|
|
*
|
|
|
|
* @see #addChild(EventNode)
|
|
|
|
* @see #removeChild(EventNode)
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(pure = true)
|
2021-06-04 03:48:51 +02:00
|
|
|
public @NotNull Set<@NotNull EventNode<T>> getChildren() {
|
|
|
|
return Collections.unmodifiableSet(children);
|
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Locates all child nodes with the given name and event type recursively starting at this node.
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The event node name to filter for
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param eventType The event node type to filter for
|
|
|
|
* @return All matching event nodes
|
|
|
|
*/
|
2021-06-08 16:55:21 +02:00
|
|
|
@Contract(pure = true)
|
|
|
|
public <E extends T> @NotNull List<EventNode<E>> findChildren(@NotNull String name, Class<E> eventType) {
|
2021-06-08 17:09:24 +02:00
|
|
|
if (children.isEmpty()) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
2021-06-08 16:55:21 +02:00
|
|
|
synchronized (GLOBAL_CHILD_LOCK) {
|
|
|
|
List<EventNode<E>> result = new ArrayList<>();
|
2021-06-10 11:53:50 +02:00
|
|
|
for (EventNode<T> child : children) {
|
2021-06-08 17:09:24 +02:00
|
|
|
if (EventNode.equals(child, name, eventType)) {
|
2021-06-08 16:55:21 +02:00
|
|
|
result.add((EventNode<E>) child);
|
|
|
|
}
|
|
|
|
result.addAll(child.findChildren(name, eventType));
|
2021-06-10 11:53:50 +02:00
|
|
|
}
|
2021-06-08 16:55:21 +02:00
|
|
|
return result;
|
|
|
|
}
|
2021-06-04 03:48:51 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Locates all child nodes with the given name and event type recursively starting at this node.
|
|
|
|
*
|
|
|
|
* @param name The event name to filter for
|
|
|
|
* @return All matching event nodes
|
|
|
|
*/
|
2021-06-08 16:55:21 +02:00
|
|
|
@Contract(pure = true)
|
|
|
|
public @NotNull List<EventNode<T>> findChildren(@NotNull String name) {
|
|
|
|
return findChildren(name, eventType);
|
2021-06-04 03:48:51 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Replaces all children matching the given name and type recursively starting from this node.
|
|
|
|
* <p>
|
|
|
|
* Node: The callee may not be replaced by this call.
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The event name to filter for
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param eventType The event node type to filter for
|
|
|
|
* @param eventNode The replacement node
|
|
|
|
*/
|
2021-06-08 17:09:24 +02:00
|
|
|
public <E extends T> void replaceChildren(@NotNull String name, @NotNull Class<E> eventType, @NotNull EventNode<E> eventNode) {
|
|
|
|
if (children.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
synchronized (GLOBAL_CHILD_LOCK) {
|
2021-06-10 11:53:50 +02:00
|
|
|
for (EventNode<T> child : children) {
|
2021-06-08 17:09:24 +02:00
|
|
|
if (EventNode.equals(child, name, eventType)) {
|
|
|
|
removeChild(child);
|
|
|
|
addChild(eventNode);
|
2021-06-10 11:53:50 +02:00
|
|
|
continue;
|
2021-06-08 17:09:24 +02:00
|
|
|
}
|
|
|
|
child.replaceChildren(name, eventType, eventNode);
|
2021-06-10 11:53:50 +02:00
|
|
|
}
|
2021-06-08 17:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Replaces all children matching the given name and type recursively starting from this node.
|
|
|
|
* <p>
|
|
|
|
* Node: The callee may not be replaced by this call.
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The node name to filter for
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param eventNode The replacement node
|
|
|
|
*/
|
2021-06-08 17:09:24 +02:00
|
|
|
public void replaceChildren(@NotNull String name, @NotNull EventNode<T> eventNode) {
|
|
|
|
replaceChildren(name, eventType, eventNode);
|
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Recursively removes children with the given name and type starting at this node.
|
|
|
|
*
|
2021-06-09 06:52:00 +02:00
|
|
|
* @param name The node name to filter for
|
2021-06-08 22:58:48 +02:00
|
|
|
* @param eventType The node type to filter for
|
|
|
|
*/
|
2021-06-09 06:52:00 +02:00
|
|
|
public void removeChildren(@NotNull String name, @NotNull Class<? extends T> eventType) {
|
2021-06-08 17:09:24 +02:00
|
|
|
if (children.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
synchronized (GLOBAL_CHILD_LOCK) {
|
2021-06-10 11:53:50 +02:00
|
|
|
for (EventNode<T> child : children) {
|
2021-06-08 17:09:24 +02:00
|
|
|
if (EventNode.equals(child, name, eventType)) {
|
|
|
|
removeChild(child);
|
2021-06-10 11:53:50 +02:00
|
|
|
continue;
|
2021-06-08 17:09:24 +02:00
|
|
|
}
|
|
|
|
child.removeChildren(name, eventType);
|
2021-06-10 11:53:50 +02:00
|
|
|
}
|
2021-06-08 17:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Recursively removes children with the given name starting at this node.
|
|
|
|
*
|
|
|
|
* @param name The node name to filter for
|
|
|
|
*/
|
2021-06-08 17:09:24 +02:00
|
|
|
public void removeChildren(@NotNull String name) {
|
|
|
|
removeChildren(name, eventType);
|
|
|
|
}
|
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Directly adds a child node to this node.
|
|
|
|
*
|
|
|
|
* @param child The child to add
|
|
|
|
* @return this, can be used for chaining
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_ -> this")
|
|
|
|
public @NotNull EventNode<T> addChild(@NotNull EventNode<? extends T> child) {
|
2021-06-03 03:37:41 +02:00
|
|
|
synchronized (GLOBAL_CHILD_LOCK) {
|
2021-06-04 00:30:48 +02:00
|
|
|
Check.stateCondition(child.parent != null, "Node already has a parent");
|
2021-06-03 09:47:48 +02:00
|
|
|
Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent");
|
2021-06-03 04:26:47 +02:00
|
|
|
final boolean result = this.children.add((EventNode<T>) child);
|
2021-06-03 03:37:41 +02:00
|
|
|
if (result) {
|
|
|
|
child.parent = this;
|
|
|
|
// Increase listener count
|
|
|
|
synchronized (lock) {
|
|
|
|
child.listenerMap.forEach((eventClass, eventListeners) -> {
|
2021-06-06 07:28:07 +02:00
|
|
|
final var entry = child.listenerMap.get(eventClass);
|
|
|
|
if (entry == null)
|
|
|
|
return;
|
|
|
|
final int childCount = entry.listeners.size() + entry.childCount;
|
|
|
|
increaseChildListenerCount(eventClass, childCount);
|
2021-06-03 03:37:41 +02:00
|
|
|
});
|
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-03 04:41:08 +02:00
|
|
|
return this;
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
2021-06-02 09:50:18 +02:00
|
|
|
|
2021-06-08 22:58:48 +02:00
|
|
|
/**
|
|
|
|
* Directly removes the given child from this node.
|
|
|
|
*
|
|
|
|
* @param child The child to remove
|
|
|
|
* @return this, can be used for chaining
|
|
|
|
*/
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_ -> this")
|
|
|
|
public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> child) {
|
2021-06-03 03:37:41 +02:00
|
|
|
synchronized (GLOBAL_CHILD_LOCK) {
|
|
|
|
final boolean result = this.children.remove(child);
|
|
|
|
if (result) {
|
|
|
|
child.parent = null;
|
|
|
|
// Decrease listener count
|
|
|
|
synchronized (lock) {
|
|
|
|
child.listenerMap.forEach((eventClass, eventListeners) -> {
|
2021-06-06 07:28:07 +02:00
|
|
|
final var entry = child.listenerMap.get(eventClass);
|
|
|
|
if (entry == null)
|
|
|
|
return;
|
|
|
|
final int childCount = entry.listeners.size() + entry.childCount;
|
|
|
|
decreaseChildListenerCount(eventClass, childCount);
|
2021-06-03 03:37:41 +02:00
|
|
|
});
|
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-03 04:41:08 +02:00
|
|
|
return this;
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_ -> this")
|
|
|
|
public @NotNull EventNode<T> addListener(@NotNull EventListener<? extends T> listener) {
|
2021-06-09 06:57:08 +02:00
|
|
|
synchronized (GLOBAL_CHILD_LOCK) {
|
|
|
|
final var eventType = listener.getEventType();
|
|
|
|
var entry = listenerMap.computeIfAbsent(eventType, aClass -> new ListenerEntry<>());
|
|
|
|
entry.listeners.add((EventListener<T>) listener);
|
|
|
|
if (parent != null) {
|
|
|
|
synchronized (parent.lock) {
|
|
|
|
parent.increaseChildListenerCount(eventType, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
2021-06-04 07:45:15 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_, _ -> this")
|
|
|
|
public <E extends T> @NotNull EventNode<T> addListener(@NotNull Class<E> eventType, @NotNull Consumer<@NotNull E> listener) {
|
2021-06-09 06:57:08 +02:00
|
|
|
return addListener(EventListener.of(eventType, listener));
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
|
2021-06-08 14:15:30 +02:00
|
|
|
@Contract(value = "_ -> this")
|
|
|
|
public @NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> listener) {
|
2021-06-03 03:37:41 +02:00
|
|
|
synchronized (GLOBAL_CHILD_LOCK) {
|
|
|
|
final var eventType = listener.getEventType();
|
2021-06-06 07:28:07 +02:00
|
|
|
var entry = listenerMap.get(eventType);
|
|
|
|
if (entry == null)
|
2021-06-03 04:41:08 +02:00
|
|
|
return this;
|
2021-06-06 07:28:07 +02:00
|
|
|
var listeners = entry.listeners;
|
2021-06-03 03:37:41 +02:00
|
|
|
final boolean removed = listeners.remove(listener);
|
|
|
|
if (removed && parent != null) {
|
|
|
|
synchronized (parent.lock) {
|
2021-06-06 07:28:07 +02:00
|
|
|
parent.decreaseChildListenerCount(eventType, 1);
|
2021-06-03 03:37:41 +02:00
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
}
|
2021-06-03 04:41:08 +02:00
|
|
|
return this;
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
|
2021-06-06 07:28:07 +02:00
|
|
|
private void increaseChildListenerCount(Class<? extends T> eventClass, int count) {
|
|
|
|
var entry = listenerMap.computeIfAbsent(eventClass, aClass -> new ListenerEntry<>());
|
|
|
|
ListenerEntry.addAndGet(entry, count);
|
2021-06-06 07:55:30 +02:00
|
|
|
if (parent != null) {
|
|
|
|
parent.increaseChildListenerCount(eventClass, count);
|
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
|
|
|
|
2021-06-06 07:28:07 +02:00
|
|
|
private void decreaseChildListenerCount(Class<? extends T> eventClass, int count) {
|
|
|
|
var entry = listenerMap.computeIfAbsent(eventClass, aClass -> new ListenerEntry<>());
|
|
|
|
final int result = ListenerEntry.addAndGet(entry, -count);
|
|
|
|
if (result == 0 && entry.listeners.isEmpty()) {
|
|
|
|
this.listenerMap.remove(eventClass);
|
|
|
|
} else if (result < 0) {
|
2021-06-03 03:27:30 +02:00
|
|
|
throw new IllegalStateException("Something wrong happened, listener count: " + result);
|
|
|
|
}
|
2021-06-06 07:55:30 +02:00
|
|
|
if (parent != null) {
|
|
|
|
parent.decreaseChildListenerCount(eventClass, count);
|
|
|
|
}
|
2021-06-03 03:27:30 +02:00
|
|
|
}
|
2021-06-06 07:28:07 +02:00
|
|
|
|
2021-06-08 17:09:24 +02:00
|
|
|
private static boolean equals(EventNode<?> node, String name, Class<?> eventType) {
|
|
|
|
final boolean nameCheck = node.getName().equals(name);
|
2021-06-08 17:18:00 +02:00
|
|
|
final boolean typeCheck = eventType.isAssignableFrom(node.eventType);
|
2021-06-08 17:09:24 +02:00
|
|
|
return nameCheck && typeCheck;
|
|
|
|
}
|
|
|
|
|
2021-06-06 07:28:07 +02:00
|
|
|
private static class ListenerEntry<T extends Event> {
|
|
|
|
private static final AtomicIntegerFieldUpdater<ListenerEntry> CHILD_UPDATER =
|
|
|
|
AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount");
|
|
|
|
|
|
|
|
List<EventListener<T>> listeners = new CopyOnWriteArrayList<>();
|
|
|
|
volatile int childCount;
|
|
|
|
|
|
|
|
private static int addAndGet(ListenerEntry<?> entry, int add) {
|
|
|
|
return CHILD_UPDATER.addAndGet(entry, add);
|
|
|
|
}
|
|
|
|
}
|
2021-06-02 08:17:21 +02:00
|
|
|
}
|