package net.minestom.server.event; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagReadable; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Predicate; public class EventNode { @Contract(value = "_ -> new", pure = true) public static @NotNull EventNode all(@NotNull String name) { return type(name, EventFilter.ALL); } @Contract(value = "_, _, _ -> new", pure = true) public static @NotNull EventNode type(@NotNull String name, @NotNull EventFilter filter, @NotNull BiPredicate predicate) { return create(name, filter, predicate); } @Contract(value = "_, _ -> new", pure = true) public static @NotNull EventNode type(@NotNull String name, @NotNull EventFilter filter) { return create(name, filter, null); } @Contract(value = "_, _, _ -> new", pure = true) public static @NotNull EventNode event(@NotNull String name, @NotNull EventFilter filter, @NotNull Predicate predicate) { return create(name, filter, (e, h) -> predicate.test(e)); } @Contract(value = "_, _, _ -> new", pure = true) public static @NotNull EventNode value(@NotNull String name, @NotNull EventFilter filter, @NotNull Predicate predicate) { return create(name, filter, (e, h) -> predicate.test(h)); } @Contract(value = "_, _, _ -> new", pure = true) public static @NotNull EventNode tag(@NotNull String name, @NotNull EventFilter filter, @NotNull Tag tag) { return create(name, filter, (e, h) -> h.hasTag(tag)); } @Contract(value = "_, _, _, _ -> new", pure = true) public static @NotNull EventNode tag(@NotNull String name, @NotNull EventFilter filter, @NotNull Tag tag, @NotNull Predicate<@Nullable V> consumer) { return create(name, filter, (e, h) -> consumer.test(h.getTag(tag))); } private static EventNode create(@NotNull String name, @NotNull EventFilter filter, @Nullable BiPredicate predicate) { return new EventNode<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null); } private static final Object GLOBAL_CHILD_LOCK = new Object(); private final Object lock = new Object(); private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); private final Set> children = new CopyOnWriteArraySet<>(); protected final String name; protected final EventFilter filter; protected final BiPredicate predicate; protected final Class eventType; private volatile int priority; private volatile EventNode parent; protected EventNode(@NotNull String name, @NotNull EventFilter filter, @Nullable BiPredicate predicate) { this.name = name; this.filter = filter; this.predicate = predicate; this.eventType = filter.getEventType(); } /** * Condition to enter the node. * * @param event the called event * @return true to enter the node, false otherwise */ protected boolean condition(@NotNull T event) { if (predicate == null) return true; final var value = filter.getHandler(event); return predicate.test(event, value); } public void call(@NotNull T event) { final var eventClass = event.getClass(); if (!eventType.isAssignableFrom(eventClass)) { // Invalid event type return; } if (!condition(event)) { // Cancelled by superclass return; } // Process listener list final var entry = listenerMap.get(eventClass); if (entry == null) { // No listener nor children return; } final var listeners = entry.listeners; if (!listeners.isEmpty()) { for (EventListener listener : listeners) { final EventListener.Result result = listener.run(event); if (result == EventListener.Result.EXPIRED) { listeners.remove(listener); } } } // Process children if (entry.childCount > 0) { this.children.stream() .sorted(Comparator.comparing(EventNode::getPriority)) .forEach(child -> child.call(event)); } } public void callCancellable(@NotNull T event, @NotNull Runnable successCallback) { call(event); if (!(event instanceof CancellableEvent) || !((CancellableEvent) event).isCancelled()) { successCallback.run(); } } @Contract(pure = true) public @NotNull String getName() { return name; } @Contract(pure = true) public int getPriority() { return priority; } @Contract(value = "_ -> this") public @NotNull EventNode setPriority(int priority) { this.priority = priority; return this; } @Contract(pure = true) public @Nullable EventNode getParent() { return parent; } @Contract(pure = true) public @NotNull Set<@NotNull EventNode> getChildren() { return Collections.unmodifiableSet(children); } @Contract(pure = true) public @NotNull List> findChildren(@NotNull String name, Class eventType) { if (children.isEmpty()) { return Collections.emptyList(); } synchronized (GLOBAL_CHILD_LOCK) { List> result = new ArrayList<>(); this.children.forEach(child -> { if (EventNode.equals(child, name, eventType)) { result.add((EventNode) child); } result.addAll(child.findChildren(name, eventType)); }); return result; } } @Contract(pure = true) public @NotNull List> findChildren(@NotNull String name) { return findChildren(name, eventType); } public void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode) { if (children.isEmpty()) { return; } synchronized (GLOBAL_CHILD_LOCK) { this.children.forEach(child -> { if (EventNode.equals(child, name, eventType)) { removeChild(child); addChild(eventNode); return; } child.replaceChildren(name, eventType, eventNode); }); } } public void replaceChildren(@NotNull String name, @NotNull EventNode eventNode) { replaceChildren(name, eventType, eventNode); } public void removeChildren(@NotNull String name, @NotNull Class eventType) { if (children.isEmpty()) { return; } synchronized (GLOBAL_CHILD_LOCK) { this.children.forEach(child -> { if (EventNode.equals(child, name, eventType)) { removeChild(child); return; } child.removeChildren(name, eventType); }); } } public void removeChildren(@NotNull String name) { removeChildren(name, eventType); } @Contract(value = "_ -> this") public @NotNull EventNode addChild(@NotNull EventNode child) { synchronized (GLOBAL_CHILD_LOCK) { Check.stateCondition(child.parent != null, "Node already has a parent"); Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent"); final boolean result = this.children.add((EventNode) child); if (result) { child.parent = this; // Increase listener count synchronized (lock) { child.listenerMap.forEach((eventClass, eventListeners) -> { final var entry = child.listenerMap.get(eventClass); if (entry == null) return; final int childCount = entry.listeners.size() + entry.childCount; increaseChildListenerCount(eventClass, childCount); }); } } } return this; } @Contract(value = "_ -> this") public @NotNull EventNode removeChild(@NotNull EventNode child) { 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) -> { final var entry = child.listenerMap.get(eventClass); if (entry == null) return; final int childCount = entry.listeners.size() + entry.childCount; decreaseChildListenerCount(eventClass, childCount); }); } } } return this; } @Contract(value = "_ -> this") public @NotNull EventNode addListener(@NotNull EventListener listener) { return addListener0(listener); } @Contract(value = "_, _ -> this") public @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener) { return addListener0(EventListener.of(eventType, listener)); } @Contract(value = "_ -> this") public @NotNull EventNode removeListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { final var eventType = listener.getEventType(); var entry = listenerMap.get(eventType); if (entry == null) return this; var listeners = entry.listeners; final boolean removed = listeners.remove(listener); if (removed && parent != null) { synchronized (parent.lock) { parent.decreaseChildListenerCount(eventType, 1); } } } return this; } private EventNode addListener0(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { final var eventType = listener.getEventType(); var entry = listenerMap.computeIfAbsent(eventType, aClass -> new ListenerEntry<>()); entry.listeners.add((EventListener) listener); if (parent != null) { synchronized (parent.lock) { parent.increaseChildListenerCount(eventType, 1); } } } return this; } private void increaseChildListenerCount(Class eventClass, int count) { var entry = listenerMap.computeIfAbsent(eventClass, aClass -> new ListenerEntry<>()); ListenerEntry.addAndGet(entry, count); if (parent != null) { parent.increaseChildListenerCount(eventClass, count); } } private void decreaseChildListenerCount(Class 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) { throw new IllegalStateException("Something wrong happened, listener count: " + result); } if (parent != null) { parent.decreaseChildListenerCount(eventClass, count); } } private static boolean equals(EventNode node, String name, Class eventType) { final boolean nameCheck = node.getName().equals(name); final boolean typeCheck = eventType.isAssignableFrom(node.eventType); return nameCheck && typeCheck; } private static class ListenerEntry { private static final AtomicIntegerFieldUpdater CHILD_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount"); List> listeners = new CopyOnWriteArrayList<>(); volatile int childCount; private static int addAndGet(ListenerEntry entry, int add) { return CHILD_UPDATER.addAndGet(entry, add); } } }