From 8b475887e5d523c6211463d27e95b4dd5fefbb36 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 18:29:23 -0400 Subject: [PATCH 01/11] Allow JavaDocs to generate with the "note" tags --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index 10b07fd..e3082c5 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,13 @@ changelog { from '1.0.0' } +tasks.withType(Javadoc).configureEach { + options { StandardJavadocDocletOptions options -> + options.windowTitle = 'EventBus ' + project.version + options.tags 'apiNote:a:API Note:', 'implNote:a:Implementation Note:', 'implSpec:a:Implementation Specification:' + } +} + tasks.withType(JavaCompile).configureEach { // Set up compile-time enforcement of the jSpecify spec via ErrorProne and NullAway options.errorprone { ErrorProneOptions errorProne -> From 207ed4c85f9793ecdc7b63f09b3b36332cad63c5 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 18:39:43 -0400 Subject: [PATCH 02/11] Prevent subprojects from overriding the root's JavaDocs --- build.gradle | 1 + eventbus-jmh/build.gradle | 4 ++++ eventbus-test-jar/build.gradle | 4 ++++ eventbus-test/build.gradle | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/build.gradle b/build.gradle index e3082c5..4c0b170 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ java { toolchain.languageVersion = JavaLanguageVersion.of(21) modularity.inferModulePath = true withSourcesJar() + withJavadocJar() } repositories { diff --git a/eventbus-jmh/build.gradle b/eventbus-jmh/build.gradle index 09b5b44..4e8da6e 100644 --- a/eventbus-jmh/build.gradle +++ b/eventbus-jmh/build.gradle @@ -36,6 +36,10 @@ extraJavaModuleInfo { automaticModule('net.sf.jopt-simple:jopt-simple', 'jopt.simple') } +tasks.named('javadoc', Javadoc) { + enabled = false +} + tasks.register('aggregateJmh', AggregateJmh) { if (rootProject.file('jmh_data_input.json').exists()) inputData = rootProject.file('jmh_data_input.json') diff --git a/eventbus-test-jar/build.gradle b/eventbus-test-jar/build.gradle index 85b027b..6f0f3be 100644 --- a/eventbus-test-jar/build.gradle +++ b/eventbus-test-jar/build.gradle @@ -33,6 +33,10 @@ license { newLine = false } +tasks.named('javadoc', Javadoc) { + enabled = false +} + // Hack eclipse into knowing that the gradle deps are modules eclipse.classpath { containers 'org.eclipse.buildship.core.gradleclasspathcontainer' diff --git a/eventbus-test/build.gradle b/eventbus-test/build.gradle index 1fef6cf..9566947 100644 --- a/eventbus-test/build.gradle +++ b/eventbus-test/build.gradle @@ -33,6 +33,10 @@ extraJavaModuleInfo { failOnMissingModuleInfo = false } +tasks.named('javadoc', Javadoc) { + enabled = false +} + tasks.named('test', Test) { useJUnitPlatform() } From 608c66c92f8125774b98709fb28743d7bf0a0183 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 18:40:00 -0400 Subject: [PATCH 03/11] Fix JavaDocs not using links to external libraries --- build.gradle | 1 + settings.gradle | 2 ++ 2 files changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 4c0b170..790c840 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ plugins { id 'org.gradlex.extra-java-module-info' version '1.11' id 'net.minecraftforge.gradleutils' version '2.4.13' id 'net.minecraftforge.licenser' version '1.1.1' + alias libs.plugins.javadoc.links // Enforce jSpecify annotations at compile-time id 'net.ltgt.errorprone' version '4.1.0' diff --git a/settings.gradle b/settings.gradle index 8aa86bb..b93b65a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,6 +12,8 @@ plugins { dependencyResolutionManagement { versionCatalogs { libs { + plugin 'javadoc-links', 'io.freefair.javadoc-links' version '8.13.1' + // https://mvnrepository.com/artifact/org.jspecify/jspecify library('jspecify-annotations', 'org.jspecify', 'jspecify') version '1.0.0' From 3931a63591a88865acf0b06b3a58fe4895a50686 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 18:28:18 -0400 Subject: [PATCH 04/11] JavaDocs - EventBus module The JavaDocs for the module file are consumed by the JavaDocs tool and is effectively displayed as the index for the EventBus JavaDocs. --- README.md | 17 ++++++++------ src/main/java/module-info.java | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1ef7678..6a60c68 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ + + # EventBus A flexible, high-performance, thread-safe subscriber-publisher framework designed with modern Java in mind. @@ -14,13 +17,13 @@ First, add the Forge Maven repository and the EventBus dependency to your projec ```gradle repositories { maven { - name = "Forge" - url = "https://maven.minecraftforge.net" + name = 'Forge' + url = 'https://maven.minecraftforge.net' } } dependencies { - implementation "net.minecraftforge:eventbus:" + implementation 'net.minecraftforge:eventbus:' } ``` @@ -47,11 +50,11 @@ Browse the `net.minecraftforge.eventbus.api` package and read the Javadocs for m examples, check out Forge's extensive use of EventBus [here][Forge usages]. ## Nullability -The entirety of EventBus' API is `@NullMarked` and compliant with the [jSpecify specification](https://jspecify.dev/) - -this means that everything is non-null by default unless otherwise specified. +The entirety of EventBus' API is `@NullMarked` and compliant with the [jSpecify specification](https://jspecify.dev/). +This means that everything is non-null by default unless otherwise specified. -Attempting to pass a `null` value to a method param that isn't explicitly marked as `@Nullable` is an unsupported -operation and won't be considered a breaking change if a future version throws an exception in such cases when it didn't +Attempting to pass a `null` value to a method param that isn't explicitly marked as `@Nullable` is an *unsupported +operation* and won't be considered a breaking change if a future version throws an exception in such cases when it didn't before. ## Contributing diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a4fba2c..88da016 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -4,6 +4,48 @@ */ import org.jspecify.annotations.NullMarked; +/** + * EventBus is a flexible, high-performance, thread-safe subscriber-publisher framework designed with modern Java in + * mind. + * + *

Overview

+ *

The core functionality of EventBus is to provide a simple and efficient way to handle + * {@linkplain net.minecraftforge.eventbus.api.event events} in a decoupled manner.

+ *

Each event may have one or more {@linkplain net.minecraftforge.eventbus.api.bus.EventBus buses} associated with + * it, which are responsible for managing {@linkplain net.minecraftforge.eventbus.api.listener.EventListener listeners} + * and dispatching instances of the event object to them. To maximise performance, the underlying implementation is + * tailored on the fly based on the event's type, + * {@linkplain net.minecraftforge.eventbus.api.event.characteristic characteristics}, inheritance chain and the number + * and type of listeners registered to the bus.

+ * + *

Example

+ *

Here is a basic usage example of EventBus in action:

+ * {@snippet : + * import net.minecraftforge.eventbus.api.event.RecordEvent; + * import net.minecraftforge.eventbus.api.bus.EventBus; + * + * // Define an event and a bus for it + * record PlayerLoggedInEvent(String username) implements RecordEvent { + * public static final EventBus BUS = EventBus.create(PlayerLoggedInEvent.class); + * } + * + * // Register an event listener + * PlayerLoggedInEvent.BUS.addListener(event -> System.out.println("Player logged in: " + event.username())); + * + * // Post an event to the registered listeners + * PlayerLoggedInEvent.BUS.post(new PlayerLoggedInEvent("Paint_Ninja")); + *} + *

There are several more example usages within the JavaDocs of the different packages and classes in this API + * module. These examples are non-exhaustive, but provide a good basis on which to build your usage of EventBus.

+ * + *

Nullability

+ *

The entirety of EventBus' API is {@link org.jspecify.annotations.NullMarked @NullMarked} and compliant with the + * jSpecify specification. This means that everything is + * {@linkplain org.jspecify.annotations.NonNull non-null} by default unless otherwise specified.

+ *

Attempting to pass a {@code null} value to a method param that isn't explicitly marked as + * {@link org.jspecify.annotations.Nullable @Nullable} is an unsupported operation and won't be considered a + * breaking change if a future version throws an exception in such cases when it didn't before.

+ */ @NullMarked module net.minecraftforge.eventbus { requires java.logging; From e49096bbb1d1342cdbac0d729ee5909427a4b81d Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 18:29:07 -0400 Subject: [PATCH 05/11] JavaDocs - BusGroup Expanded on the documentation for BusGroup, including a non-exhaustive simple example for the class JavaDoc and expanded explanations for the methods. --- .../eventbus/api/bus/BusGroup.java | 142 +++++++++++++++--- 1 file changed, 122 insertions(+), 20 deletions(-) diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java b/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java index 3bb9559..71388e3 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java @@ -4,89 +4,191 @@ */ package net.minecraftforge.eventbus.api.bus; -import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.api.listener.EventListener; import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.eventbus.internal.BusGroupImpl; +import net.minecraftforge.eventbus.internal.Event; import java.lang.invoke.MethodHandles; import java.util.Collection; /** - * A collection of {@link EventBus} instances that are grouped together for easier management. + * A bus group is a collection of {@link EventBus} instances that are grouped together for easier management. + *

Using a bus group allows consumers to manage all of their related event buses without needing to manually manage + * each one.

+ * + *

Example

+ *

Here is a small example showing the creation and disposal of a bus group.

+ * {@snippet : + * import net.minecraftforge.eventbus.api.bus.BusGroup; + * import net.minecraftforge.eventbus.api.bus.EventBus; + * import net.minecraftforge.eventbus.api.event.RecordEvent; + * import net.minecraftforge.eventbus.api.listener.SubscribeEvent; + * + * import java.lang.invoke.MethodHandles; + * + * public class MyClass { + * public static final BusGroup BUS_GROUP = BusGroup.create("MyProject", RecordEvent.class); + * + * public record MyEvent(String message) implements RecordEvent { + * public static final EventBus BUS = EventBus.create(BUS_GROUP, MyEvent.class); + * } + * + * @SubscribeEvent + * private static void onMyEvent(MyEvent event) { + * System.out.println("Received event: " + event.message()); + * } + * + * // if we only have one listener in our class, EventBus will throw an exception saying you should use BusGroup#addListener instead + * @SubscribeEvent + * private static void alsoOnMyEvent(MyEvent event) { + * System.out.println("Double checking, received event: " + event.message()); + * } + * + * // begin program! + * public static void run() { + * // the bus group is already started! no need to call startup() on it. + * + * // MethodHandles.lookup() gives EventBus the ability to get method references + * // for all the @SubscribeEvent methods in this class. + * BUS_GROUP.register(MethodHandles.lookup(), MyClass.class); + * } + * + * // close program! + * public static void shutdown() { + * // dispose will shutdown and then dispose this bus group + * // consider it "freed memory" that should not be reused + * BUS_GROUP.dispose(); + * } + * } + *} */ public sealed interface BusGroup permits BusGroupImpl { + /** + * The default bus group, which is used when an {@linkplain EventBus event bus} is created without specifying a + * group. + * + * @apiNote If you require tight controls over your event buses, you should create your own bus group instead. This + * bus group can be used and mutated by other consumers within the same environment. + * @see EventBus#create(Class) + */ BusGroup DEFAULT = create("default"); + /** + * Creates a new bus group with the given name. + *

The name for this bus group must be unique. An attempt to create a bus group with a name that is + * already in use will result in an {@link IllegalArgumentException}. If you must create a new bus group with a name + * that is in use, the relevant bus group must be {@linkplain #dispose() disposed}.

+ * + * @param name The name + * @return The new bus group + * @throws IllegalArgumentException If the name is already in use by another bus group + * @apiNote To enforce a base type with your bus group, use {@linkplain #create(String, Class)}. + */ static BusGroup create(String name) { return new BusGroupImpl(name, Event.class); } + /** + * Creates a new bus group with the given name. + *

The given base type will enforce that all {@linkplain EventBus event buses} created within this group inherit + * it.

+ *

The name for this bus group must be unique. An attempt to create a bus group with a name that is + * already in use will result in an {@link IllegalArgumentException}. If you must create a new bus group with a name + * that is in use, the relevant bus group must be {@linkplain #dispose() disposed}.

+ * + * @param name The name + * @return The new bus group + * @throws IllegalArgumentException If the name is already in use by another bus group + */ static BusGroup create(String name, Class baseType) { return new BusGroupImpl(name, baseType); } /** * The unique name of this BusGroup. + *

The uniqueness of this name is enforced when the bus group is {@linkplain #create(String) created}.

*/ String name(); /** - * Starts up all EventBus instances associated with this BusGroup, allowing events to be posted again after a + * Starts up all EventBus instances associated with this bus group, allowing events to be posted again after a * previous call to {@link #shutdown()}. + *

Calling this method without having previously called {@link #shutdown()} will have no effect.

*/ void startup(); /** - * Shuts down all EventBus instances associated with this BusGroup, preventing any further events from being posted + * Shuts down all EventBus instances associated with this bus group, preventing any further events from being posted * until {@link #startup()} is called. + *

Calling this method without having previously called {@link #startup()} will have no effect.

+ * + * @apiNote If you need to destroy this bus group and free up the resources it uses, use {@link #dispose()}. */ void shutdown(); /** - * Shuts down all EventBus instances associated with this BusGroup, unregisters all listeners and frees resources - * no longer needed. - *

Warning: This is a destructive operation - this BusGroup should not be used again after calling this method.

+ * {@linkplain #shutdown() Shuts down} all EventBus instances associated with this bus group, + * {@linkplain #unregister(Collection) unregisters} all listeners and frees resources no longer needed. + *

This will effectively destroy this bus group. It should not be used again after calling this + * method.

+ * + * @apiNote If you plan on using this bus group again, use {@link #shutdown()} instead. */ void dispose(); /** - * Experimental feature - may be removed, renamed or otherwise changed without notice. - *

Trims the backing lists of all EventBus instances associated with this BusGroup to free up resources.

- *

Warning: This is only intended to be called once after all listeners are registered - calling this + * Trims the backing lists of all EventBus instances associated with this BusGroup to free up resources. + *

This is only intended to be called once after all listeners are registered. Calling this * repeatedly may hurt performance.

+ * + * @apiNote This is an experimental feature! It may be removed, renamed or otherwise changed + * without notice. */ void trim(); /** - * Registers all static methods annotated with {@link SubscribeEvent} in the given class. + * Registers all static methods annotated with {@link SubscribeEvent} in the given class. + *

This is done by getting method references for those methods using the given + * {@linkplain MethodHandles.Lookup method handles lookup}. This lookup must be acquiored from + * {@link MethodHandles#lookup()}. Using {@link MethodHandles#publicLookup()} is unsupported because it + * doesn't work with {@link java.lang.invoke.LambdaMetafactory} as it could allow for access to private fields + * through inner class generation.

* - * @param callerLookup {@code MethodHandles.lookup()} from the class containing listeners + * @param callerLookup {@link MethodHandles#lookup()} from the class containing listeners * @param utilityClassWithStaticListeners the class containing the static listeners * @return A collection of the registered listeners, which can be used to optionally unregister them later - * * @apiNote This method only registers static listeners. - *

If you want to register both instance and static methods, use - * {@link BusGroup#register(MethodHandles.Lookup, Object)} instead.

+ *

If you want to register both instance and static methods, use + * {@link BusGroup#register(MethodHandles.Lookup, Object)} instead.

*/ Collection register(MethodHandles.Lookup callerLookup, Class utilityClassWithStaticListeners); /** * Registers all methods annotated with {@link SubscribeEvent} in the given object. + *

Both the static and instance methods for the given object are registered. Keep in mind that, unlike + * with {@link #register(MethodHandles.Lookup, Class)}, you will need to register each object instance of the class + * using this method.

+ *

This is done by getting method references for those methods using the given + * {@linkplain MethodHandles.Lookup method handles lookup}. This lookup must be acquiored from + * {@link MethodHandles#lookup()}. Using {@link MethodHandles#publicLookup()} is unsupported because it + * doesn't work with {@link java.lang.invoke.LambdaMetafactory} as it could allow for access to private fields + * through inner class generation.

* * @param callerLookup {@code MethodHandles.lookup()} from the class containing the listeners - * @param listener the object containing the static and/or instance listeners + * @param listener the object containing the static and/or instance listeners * @return A collection of the registered listeners, which can be used to optionally unregister them later - * * @apiNote If you know all the listeners are static methods, use - * {@link BusGroup#register(MethodHandles.Lookup, Class)} instead for better registration performance. + * {@link BusGroup#register(MethodHandles.Lookup, Class)} instead for better registration performance. */ Collection register(MethodHandles.Lookup callerLookup, Object listener); /** - * Unregisters the given listeners from this BusGroup. + * Unregisters the given listeners from this bus group. + * * @param listeners A collection of listeners to unregister, obtained from - * {@link #register(MethodHandles.Lookup, Class)} or {@link #register(MethodHandles.Lookup, Object)} + * {@link #register(MethodHandles.Lookup, Class)} or + * {@link #register(MethodHandles.Lookup, Object)} */ void unregister(Collection listeners); } From 58bebab95dfb67c7f34a4986ca73af3831179523 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 19:50:08 -0400 Subject: [PATCH 06/11] JavaDocs - EventBus --- .../eventbus/api/bus/EventBus.java | 128 ++++++++++++++---- 1 file changed, 100 insertions(+), 28 deletions(-) diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java index 8daaf0b..41e9bde 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java @@ -4,75 +4,143 @@ */ package net.minecraftforge.eventbus.api.bus; -import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.api.event.characteristic.Cancellable; import net.minecraftforge.eventbus.api.listener.EventListener; import net.minecraftforge.eventbus.api.listener.Priority; import net.minecraftforge.eventbus.internal.AbstractEventBusImpl; import net.minecraftforge.eventbus.internal.BusGroupImpl; +import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.internal.EventBusImpl; import java.util.function.Consumer; +/** + * An event bus is a host of listeners for a specific event. + *

It can be thought of much like an actual bus. A bus has passengers that are all headed for the same destination, + * or at least are on the same route.

+ * + *

Usage

+ *

The key idea to understand about an event bus is that is designed to be used with a specific event tied to a + * specific {@linkplain BusGroup bus group}. While not all listeners on the event bus may behave the same, they are all + * listening for the same event.

+ *

Listeners can have different characteristics based on how it is registered (this may be subject to the event's + * {@linkplain net.minecraftforge.eventbus.api.event.characteristic characteristics}). They are registered through + * {@link #addListener(Consumer)} or one of its sister methods. Each registering method contains additional details on + * how the registered listener behaves.

+ * + *

Example

+ *

Here is a small example showing the simple registration of two event listeners with different priorities.

+ * {@snippet : + * import net.minecraftforge.eventbus.api.bus.EventBus; + * import net.minecraftforge.eventbus.api.event.MutableEvent; + * import net.minecraftforge.eventbus.api.listener.Priority; + * + * public class MyCustomEvent extends MutableEvent { + * protected static final String HELLO = "Hello, world!"; + * + * public static final EventBus BUS = EventBus.create(MyCustomEvent.class); + * + * private static void onMyCustomEvent(MyCustomEvent event) { + * System.out.println("Received custom event: " + event); + * } + * + * private static void alsoOnMyCustomEvent(MyCustomEvent event) { + * System.out.println("Received custom event (but later!): " + event); + * } + * + * public static void run() { + * BUS.addListener(MyCustomEvent::onMyCustomEvent); + * BUS.addListener(Priority.LOW, MyCustomEvent::alsoOnMyCustomEvent); + * } + * } + *} + * + *

Cancellability

+ *

Events have the ability to be {@linkplain Cancellable cancellable}. If you need to use a cancellable event, use + * {@link CancellableEventBus} instead, which include special handling for posting cancellable events and recieving the + * cancellation state. As discussed in the documentation for the cancellable characteristic, the cancellation state is + * not attached to the event instance.

+ * + * @param The event type this bus is for + */ public sealed interface EventBus permits CancellableEventBus, AbstractEventBusImpl, EventBusImpl { /** * Adds a listener to this EventBus with the default priority of {@link Priority#NORMAL}. + * * @param listener The listener to add * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} */ EventListener addListener(Consumer listener); /** - * Adds a listener to this EventBus with the given priority. - * @param priority The priority of this listener. Higher numbers are called first. + * Adds a listener to this EventBus with the given {@linkplain Priority priority}. + * + * @param priority The priority of this listener * @param listener The listener to add * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} - * @see Priority For common priority values + * @see Priority */ EventListener addListener(byte priority, Consumer listener); /** * Re-adds a listener to this EventBus that was previously removed with {@link #removeListener(EventListener)}. - * @param listener The exact same reference returned by an {@code addListener} method - * @return The same reference that was passed in + * + * @param listener The event listener returned immediately after it was initially added + * @return The listener that was re-added + * @apiNote Using this over re-adding the listener with {@link #addListener(Consumer)} is recommended for + * performance, as it removes the need to create another {@link EventListener} state. */ EventListener addListener(EventListener listener); /** - * Removes a listener from this EventBus that was previously added with one of the {@code addListener} methods. - * @param listener The exact same reference returned by an {@code addListener} method + * Removes a listener from this EventBus that was previously added with {@link #addListener(Consumer)} or one of its + * sisters. + * + * @param listener The event listener returned immediately after it was initially added */ void removeListener(EventListener listener); /** + * Posts the given event to all listeners registered to this bus. + * * @param event The instance of this event to post to listeners - * @return {@code true} if the event implements {@link Cancellable} and the event was cancelled - * by a listener + * @return {@code false} + * @apiNote This bus will always return {@code false} unless it is a + * {@linkplain CancellableEventBus cancellable event bus}. + * @see CancellableEventBus#post(Event) */ boolean post(T event); /** + * Fires the given event to all listeners registered to this bus. + *

After posting, the event itself is returned from this method. It may be mutated.

+ * * @param event The instance of this event to fire to listeners - * @return The possibly mutated event instance after all applicable listeners have been called + * @return The event after being posted */ T fire(T event); /** - * If making a new event instance is expensive, you can check against this method to avoid creating a new instance - * unnecessarily. - * @apiNote You only need to check this if event creation is expensive. If it's cheap, just call {@link #post(Event)} - * or {@link #fire(Event)} directly and let the JIT handle it. - * @return {@code true} if there are any listeners registered to this EventBus. + * Checks if this event bus has any listeners registered to it. + *

If making a new event instance is expensive, you can check against this method to avoid creating a new + * instance unnecessarily.

+ * + * @return {@code true} if there are any listeners registered + * @apiNote If event creation is cheap, you should instead call {@link #post(Event)} or {@link #fire(Event)} + * directly and let the JIT handle the side effects. */ boolean hasListeners(); /** - * Creates a new EventBus for the given event type on the default {@link BusGroup}. - *

- * Important: The returned EventBus MUST be stored in a {@code static final} field - failing to do so - * will severely hurt performance - *

- * @apiNote There can only be one EventBus instance per event type per BusGroup. + * Creates a new EventBus for the given event type on the {@linkplain BusGroup#DEFAULT default bus group}. + *

The returned EventBus must be stored in a {@code static final} field! Failing to do so will + * severely hinder performance.

+ *

Additionally, there can only be one event bus instance per event type per bus group. If an event bus already + * exists for the given type, it will be returned instead.

+ * + * @param eventType The event type for the bus + * @param The type of event this bus is for + * @return The newly-created event bus */ @SuppressWarnings("ClassEscapesDefinedScope") // E can be a subtype of Event which is publicly accessible static EventBus create(Class eventType) { @@ -80,12 +148,16 @@ static EventBus create(Class eventType) { } /** - * Creates a new EventBus for the given event type on the given {@link BusGroup}. - *

- * Important: The returned EventBus MUST be stored in a {@code static final} field - failing to do so - * will severely hurt performance - *

- * @apiNote There can only be one EventBus instance per event type per BusGroup. + * Creates a new event bus for the given event type on the given {@linkplain BusGroup bus group}. + *

The returned event bus must be stored in a {@code static final} field! Failing to do so will + * severely hinder performance.

+ *

Additionally, there can only be one event bus instance per event type per bus group. If an event bus already + * exists for the given type, it will be returned instead.

+ * + * @param busGroup The bus group to create the event bus on + * @param eventType The event type for the bus + * @param The type of event this bus is for + * @return The newly-created event bus */ @SuppressWarnings("ClassEscapesDefinedScope") // E can be a subtype of Event which is publicly accessible static EventBus create(BusGroup busGroup, Class eventType) { From 674d6c75000b51e89a01ce68782ef471d2f47929 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 20:01:28 -0400 Subject: [PATCH 07/11] Add JetBrains annotations (not for nulls) JetBrains annotations include a handful of useful documentation annotations that can improve readability in the source files (ApiStatus) and IDE support (Contract). --- build.gradle | 1 + settings.gradle | 1 + src/main/java/module-info.java | 5 +++-- .../minecraftforge/eventbus/api/bus/BusGroup.java | 4 ++++ .../eventbus/api/bus/CancellableEventBus.java | 5 +++++ .../minecraftforge/eventbus/api/bus/EventBus.java | 4 ++++ .../api/event/characteristic/MonitorAware.java | 13 ++++++++++--- .../api/event/characteristic/SelfPosting.java | 15 +++++++++++---- .../eventbus/api/listener/EventListener.java | 4 ++++ .../eventbus/internal/MutableEventInternals.java | 4 +--- 10 files changed, 44 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 790c840..99c29ce 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ repositories { dependencies { api libs.jspecify.annotations + compileOnly libs.jetbrains.annotations errorprone libs.errorprone.core errorprone libs.nullaway } diff --git a/settings.gradle b/settings.gradle index b93b65a..3da23fa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ dependencyResolutionManagement { // https://mvnrepository.com/artifact/org.jspecify/jspecify library('jspecify-annotations', 'org.jspecify', 'jspecify') version '1.0.0' + library 'jetbrains-annotations', 'org.jetbrains', 'annotations' version '26.0.2' // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core library('errorprone-core', 'com.google.errorprone', 'error_prone_core') version '2.36.0' diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 88da016..e9c79ad 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -48,8 +48,9 @@ */ @NullMarked module net.minecraftforge.eventbus { - requires java.logging; - requires org.jspecify; + requires java.logging; // Logging + requires org.jspecify; // Nullability + requires static org.jetbrains.annotations; // Other Static Analysis exports net.minecraftforge.eventbus.api.bus; exports net.minecraftforge.eventbus.api.event; diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java b/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java index 71388e3..0078baf 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/BusGroup.java @@ -8,6 +8,8 @@ import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.eventbus.internal.BusGroupImpl; import net.minecraftforge.eventbus.internal.Event; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; import java.lang.invoke.MethodHandles; import java.util.Collection; @@ -109,6 +111,7 @@ static BusGroup create(String name, Class baseType) { * The unique name of this BusGroup. *

The uniqueness of this name is enforced when the bus group is {@linkplain #create(String) created}.

*/ + @Contract(pure = true) String name(); /** @@ -145,6 +148,7 @@ static BusGroup create(String name, Class baseType) { * @apiNote This is an experimental feature! It may be removed, renamed or otherwise changed * without notice. */ + @ApiStatus.Experimental void trim(); /** diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java b/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java index c788250..ad2e768 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java @@ -11,6 +11,7 @@ import net.minecraftforge.eventbus.api.listener.Priority; import net.minecraftforge.eventbus.internal.BusGroupImpl; import net.minecraftforge.eventbus.internal.CancellableEventBusImpl; +import org.jetbrains.annotations.Contract; import java.util.function.Consumer; import java.util.function.Predicate; @@ -67,6 +68,10 @@ default EventListener addListener(boolean alwaysCancelling, Consumer listener */ EventListener addListener(ObjBooleanBiConsumer listener); + @Override + @Contract("_ -> _") + boolean post(T event); + /** * Creates a new CancellableEventBus for the given event type on the default {@link BusGroup}. *

diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java index 41e9bde..96ef5ef 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java @@ -11,6 +11,7 @@ import net.minecraftforge.eventbus.internal.BusGroupImpl; import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.internal.EventBusImpl; +import org.jetbrains.annotations.Contract; import java.util.function.Consumer; @@ -90,6 +91,7 @@ public sealed interface EventBus permits CancellableEventBus, A * @apiNote Using this over re-adding the listener with {@link #addListener(Consumer)} is recommended for * performance, as it removes the need to create another {@link EventListener} state. */ + @Contract("_ -> param1") EventListener addListener(EventListener listener); /** @@ -109,6 +111,7 @@ public sealed interface EventBus permits CancellableEventBus, A * {@linkplain CancellableEventBus cancellable event bus}. * @see CancellableEventBus#post(Event) */ + @Contract(value = "_ -> false") boolean post(T event); /** @@ -118,6 +121,7 @@ public sealed interface EventBus permits CancellableEventBus, A * @param event The instance of this event to fire to listeners * @return The event after being posted */ + @Contract(value = "_ -> param1") T fire(T event); /** diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java index 4364ab2..83db404 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java @@ -6,14 +6,21 @@ import net.minecraftforge.eventbus.api.event.MutableEvent; import net.minecraftforge.eventbus.internal.MutableEventInternals; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; /** - * Experimental feature - may be removed, renamed or otherwise changed without notice. - *

Events that are {@link MonitorAware} can provide stronger immutability guarantees to monitor listeners by - * returning unmodifiable views or throwing exceptions on mutation attempts when monitoring.

+ * Events that are {@link MonitorAware} can provide stronger immutability guarantees to monitor listeners by returning + * unmodifiable views or throwing exceptions on mutation attempts when monitoring. *

Only supported for {@link MutableEvent} at this time.

+ * + * @apiNote This is an experimental feature! It may be removed, renamed or otherwise changed without + * notice. */ +@ApiStatus.Experimental public non-sealed interface MonitorAware extends EventCharacteristic { + @Contract(pure = true) + @ApiStatus.NonExtendable default boolean isMonitoring() { assert this instanceof MutableEvent; // note: MutableEvent extends MutableEventInternals return ((MutableEventInternals) this).isMonitoring; diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java index 8774fed..03140d3 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java @@ -6,12 +6,14 @@ import net.minecraftforge.eventbus.api.bus.EventBus; import net.minecraftforge.eventbus.internal.Event; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; /** - * Experimental feature - may be removed, renamed or otherwise changed without notice. - *

{@link SelfPosting} events are associated with a default {@link EventBus} in order to offer some convenience - * instance methods.

- * Example + * Self-posting events are associated with a default {@link EventBus} in order to offer some convenience instance + * methods. + * + *

Example

* {@snippet : * import net.minecraftforge.eventbus.api.event.RecordEvent; * @@ -31,7 +33,11 @@ * // instead of this * ExampleEvent.BUS.post(new ExampleEvent()); *} + * + * @apiNote This is an experimental feature! It may be removed, renamed or otherwise changed without + * notice. */ +@ApiStatus.Experimental public non-sealed interface SelfPosting extends EventCharacteristic { /** * @implSpec This should directly return a {@code static final} field without additional logic or processing. @@ -49,6 +55,7 @@ default boolean post() { /** * @see EventBus#fire(Event) */ + @Contract(value = "-> this") @SuppressWarnings("unchecked") default T fire() { return getDefaultBus().fire((T) this); diff --git a/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java b/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java index 018f47c..e7039a3 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java +++ b/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java @@ -8,6 +8,7 @@ import net.minecraftforge.eventbus.api.bus.EventBus; import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.internal.EventListenerImpl; +import org.jetbrains.annotations.Contract; import java.util.function.Consumer; @@ -19,17 +20,20 @@ * various conversion operations to different lambda types.

*/ public sealed interface EventListener permits EventListenerImpl { + @Contract(pure = true) @SuppressWarnings("ClassEscapesDefinedScope") // ? can be a subtype of Event which is publicly accessible Class eventType(); /** * @see Priority */ + @Contract(pure = true) byte priority(); /** * @see CancellableEventBus#addListener(boolean, Consumer) */ + @Contract(pure = true) default boolean alwaysCancelling() { return false; } diff --git a/src/main/java/net/minecraftforge/eventbus/internal/MutableEventInternals.java b/src/main/java/net/minecraftforge/eventbus/internal/MutableEventInternals.java index 2b2e0fc..88c0cd1 100644 --- a/src/main/java/net/minecraftforge/eventbus/internal/MutableEventInternals.java +++ b/src/main/java/net/minecraftforge/eventbus/internal/MutableEventInternals.java @@ -8,8 +8,6 @@ import net.minecraftforge.eventbus.api.event.characteristic.MonitorAware; public sealed abstract class MutableEventInternals permits MutableEvent { - /** - * @see MonitorAware - */ + /** @see MonitorAware#isMonitoring() */ public transient boolean isMonitoring; } From abbb0a040d899a15a685cc40e2c369c8318b3e31 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 22:13:04 -0400 Subject: [PATCH 08/11] JavaDocs - CancellableEventBus --- README.md | 2 +- .../eventbus/api/bus/CancellableEventBus.java | 193 ++++++++++++++---- .../eventbus/api/bus/EventBus.java | 14 +- 3 files changed, 157 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 6a60c68..53c0295 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ + `src/main/java/module-info.java` --> # EventBus diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java b/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java index ad2e768..1b8006d 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java @@ -4,97 +4,200 @@ */ package net.minecraftforge.eventbus.api.bus; -import net.minecraftforge.eventbus.internal.Event; import net.minecraftforge.eventbus.api.event.characteristic.Cancellable; import net.minecraftforge.eventbus.api.listener.EventListener; import net.minecraftforge.eventbus.api.listener.ObjBooleanBiConsumer; import net.minecraftforge.eventbus.api.listener.Priority; import net.minecraftforge.eventbus.internal.BusGroupImpl; import net.minecraftforge.eventbus.internal.CancellableEventBusImpl; -import org.jetbrains.annotations.Contract; +import net.minecraftforge.eventbus.internal.Event; import java.util.function.Consumer; import java.util.function.Predicate; +/** + * Events can have characteristics, and one such is the ability to be {@linkplain Cancellable cancelled}. The state of + * an event's cancellation, however, is not attached to the event itself, but rather returned as a result of + * {@linkplain #post(Event) posting} the event. The cancellable event bus is a specialized type of event bus that is + * designed to handle this cancellable nature of events without explicitly requiring events themselves to store their + * cancellation state. + *

For more details on the event bus in general, see {@link EventBus}.

+ * + *

Usage

+ *

Event listeners for cancellable events can have a few additional properties that set them apart from normal + * listeners. These are the ability to cancel the event or to + * {@linkplain EventListener#alwaysCancelling() always cancel} the event. The specialized + * {@link #addListener(Predicate)} method, along with its sisters in this class, are designed to give event listeners + * that characteristic.

+ * + *

Example

+ *

Here is a small example that showcases the different listeners this type of event bus can have.

+ * {@snippet : + * import net.minecraftforge.eventbus.api.bus.CancellableEventBus; + * import net.minecraftforge.eventbus.api.event.RecordEvent; + * import net.minecraftforge.eventbus.api.event.characteristic.Cancellable; + * import net.minecraftforge.eventbus.api.listener.Priority; + * + * import java.util.Random; + * + * public record MyCustomCancellableEvent() implements RecordEvent, Cancellable { + * // you MUST use the #create method in CancellableEventBus, or you won't be able to see the cancellation state from #post! + * public static final CancellableEventBus BUS = CancellableEventBus.create(MyCustomCancellableEvent.class); + * + * // a listener that does not cancel, only listens quietly + * private static void onMyCustomEvent(MyCustomCancellableEvent event) { + * System.out.println("Received custom event: " + event); + * } + * + * // a listener that might cancel the event + * private static boolean alsoOnMyCustomEvent(MyCustomCancellableEvent event) { + * System.out.println("Received custom event (but later!): " + event); + * return new Random().nextBoolean(); + * } + * + * // a listener that always cancels (registered with CancellableEventBus#addListener(true, this::method)) + * private static void alwaysCancelMyEvent(MyCustomCancellableEvent event) { + * System.err.println("We are cancelling the event!!!"); + * } + * + * private static void cannotRecieveEvent(MyCustomCancellableEvent event) { + * throw new IllegalStateException("Event wasn't cancelled??? " + event); + * } + * + * // a monitoring listener that will always receive the event, even if it is cancelled + * private static void monitoringCancelledEvent(MyCustomCancellableEvent event, boolean cancelled) { + * System.out.println("Monitoring custom event: " + event + ", cancelled: " + cancelled); + * } + * + * public static void run() { + * BUS.addListener(MyCustomCancellableEvent::onMyCustomEvent); + * BUS.addListener(Priority.LOW, MyCustomCancellableEvent::alsoOnMyCustomEvent); // might cancel + * BUS.addListener(Priority.LOWEST + 1, true, MyCustomCancellableEvent::alwaysCancelMyEvent); // will always cancel + * BUS.addListener(MyCustomCancellableEvent::cannotRecieveEvent); // will never be called + * BUS.addListener(MyCustomCancellableEvent::monitoringCancelledEvent); // monitors events, even cancelled ones + * } + * } + *} + * + * @param The type of cancellable event for this bus + */ public sealed interface CancellableEventBus - extends EventBus permits CancellableEventBusImpl { + extends EventBus permits CancellableEventBusImpl { /** - * Adds an always cancelling listener to this EventBus with the default priority of {@link Priority#NORMAL}. - * @param alwaysCancelling If true, always cancel the event after calling the listener. This acts as if you - * added a Predicate listener that always returns true, but with additional optimisations. - *

If false, you should use {@link #addListener(Consumer)} instead to avoid unnecessary - * breaking changes if the event is no longer cancellable in the future.

- * @param listener The listener to add. - * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)}. - * @see #addListener(Predicate) For adding a listener that can cancel the event conditionally - * @see #addListener(Consumer) For adding a listener that never cancels the event + * Adds a listener to this EventBus with the default priority of {@link Priority#NORMAL}. + *

This listener, based on the given boolean, may always cancel the event when it is invoked. If it is + * {@code true}, this method acts as if you {@linkplain #addListener(Predicate) added a predicate listener} that + * always returns {@code true}, but with additional optimisations.

+ *

If you plan on passing in {@code false} instead, you should instead consider using + * {@link #addListener(Consumer)}.

+ * + * @param alwaysCancelling Whether to always cancel the event after calling the listener + * @param listener The listener to add + * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} */ default EventListener addListener(boolean alwaysCancelling, Consumer listener) { return addListener(Priority.NORMAL, alwaysCancelling, listener); } /** - * Adds an always cancelling listener to this EventBus with the specified priority. - * @param alwaysCancelling If true, always cancel the event after calling the listener. This acts as if you - * added a Predicate listener that always returns true, but with additional optimisations. - *

If false, you should use {@link #addListener(byte, Consumer)} instead to avoid - * unnecessary breaking changes if the event is no longer cancellable in the future

- * @param listener The listener to add. - * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)}. - * @see Priority For common priority values + * Adds a listener to this EventBus with the given {@linkplain Priority priority}. + *

This listener, based on the given boolean, may always cancel the event when it is invoked. If it is + * {@code true}, this method acts as if you {@linkplain #addListener(Predicate) added a predicate listener} that + * always returns {@code true}, but with additional optimisations.

+ *

If you plan on passing in {@code false} instead, you should instead consider using + * {@link #addListener(Consumer)}.

+ * + * @param priority The priority for the listener + * @param alwaysCancelling Whether to always cancel the event after calling the listener + * @param listener The listener to add + * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} */ EventListener addListener(byte priority, boolean alwaysCancelling, Consumer listener); /** - * Adds a possibly cancelling listener to this EventBus with the default priority of {@link Priority#NORMAL}. - * @param listener The listener to add. - * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)}. + * Adds a listener to this EventBus with the default priority of {@link Priority#NORMAL}. + *

The predicate listener can return {@code true} to cancel the event.

+ * + * @param listener The listener to add + * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} */ EventListener addListener(Predicate listener); /** - * Adds a possibly cancelling listener to this EventBus with the specified priority. - * @param priority The priority of this listener. Higher numbers are called first. - * @param listener The listener to add. - * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)}. + * Adds a listener to this EventBus with the given {@linkplain Priority priority}. + *

The predicate listener can return {@code true} to cancel the event.

+ * + * @param priority The priority for the listener + * @param listener The listener to add + * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} * @see Priority For common priority values */ EventListener addListener(byte priority, Predicate listener); /** * Adds a cancellation-aware monitoring listener to this EventBus. - * @param listener The listener to add. - * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)}. + *

This listener will always run at a priority of {@link Priority#MONITOR} and will always receive cancelled + * events.

+ * + * @param listener The listener to add + * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} + * @see Priority#MONITOR */ EventListener addListener(ObjBooleanBiConsumer listener); + /** + * Posts the given event to all listeners registered to this bus. + *

Unlike {@link EventBus#post(Event)}, the cancellation state of the posted event is returned by this + * method.

+ * + * @param event The instance of this event to post to listeners + * @return {@code true} if the event was cancelled + */ @Override - @Contract("_ -> _") boolean post(T event); /** - * Creates a new CancellableEventBus for the given event type on the default {@link BusGroup}. - *

- * Important: The returned EventBus MUST be stored in a {@code static final} field - failing to do so - * will severely hurt performance - *

- * @apiNote There can only be one EventBus instance per event type per BusGroup. + * {@inheritDoc} + * + * @deprecated Using this method with a cancellable event bus is not recommended, as it does not capture the event's + * cancellation state. Use {@link #post(Event)} instead. + */ + @Deprecated + @Override + T fire(T event); + + /** + * Creates a new cancellable event bus for the given {@linkplain net.minecraftforge.eventbus.api.event event} type + * on the {@linkplain BusGroup#DEFAULT default bus group}. + *

The returned EventBus must be stored in a {@code static final} field! Failing to do so will + * severely hinder performance.

+ *

Additionally, there can only be one event bus instance per event type per bus group. If an event bus already + * exists for the given type, it will be returned instead.

+ * + * @param eventType The cancellable event type for the bus + * @param The type of cancellable event this bus is for + * @return The newly-created event bus */ @SuppressWarnings("ClassEscapesDefinedScope") // E can be a subtype of Event which is publicly accessible - static CancellableEventBus create(Class eventType) { + static CancellableEventBus create(Class eventType) { return create(BusGroup.DEFAULT, eventType); } /** - * Creates a new CancellableEventBus for the given event type on the given {@link BusGroup}. - *

- * Important: The returned EventBus MUST be stored in a {@code static final} field - failing to do so - * will severely hurt performance - *

- * @apiNote There can only be one EventBus instance per event type per BusGroup. + * Creates a new cancellable event bus for the given {@linkplain Cancellable cancellable} + * {@linkplain net.minecraftforge.eventbus.api.event event} type on the given {@linkplain BusGroup bus group}. + *

The returned EventBus must be stored in a {@code static final} field! Failing to do so will + * severely hinder performance.

+ *

Additionally, there can only be one event bus instance per event type per bus group. If an event bus already + * exists for the given type, it will be returned instead.

+ * + * @param busGroup The bus group to create the event bus on + * @param eventType The cancellable event type for the bus + * @param The type of cancellable event this bus is for + * @return The newly-created event bus */ @SuppressWarnings("ClassEscapesDefinedScope") // E can be a subtype of Event which is publicly accessible - static CancellableEventBus create(BusGroup busGroup, Class eventType) { - return (CancellableEventBus) ((BusGroupImpl) busGroup).getOrCreateEventBus(eventType); + static CancellableEventBus create(BusGroup busGroup, Class eventType) { + return (CancellableEventBus) ((BusGroupImpl) busGroup).getOrCreateEventBus(eventType); } } diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java index 96ef5ef..9ef2c3f 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/EventBus.java @@ -62,7 +62,7 @@ * cancellation state. As discussed in the documentation for the cancellable characteristic, the cancellation state is * not attached to the event instance.

* - * @param The event type this bus is for + * @param The type of event for this bus */ public sealed interface EventBus permits CancellableEventBus, AbstractEventBusImpl, EventBusImpl { /** @@ -76,7 +76,7 @@ public sealed interface EventBus permits CancellableEventBus, A /** * Adds a listener to this EventBus with the given {@linkplain Priority priority}. * - * @param priority The priority of this listener + * @param priority The priority for the listener * @param listener The listener to add * @return A reference that can be used to remove this listener later with {@link #removeListener(EventListener)} * @see Priority @@ -106,12 +106,12 @@ public sealed interface EventBus permits CancellableEventBus, A * Posts the given event to all listeners registered to this bus. * * @param event The instance of this event to post to listeners - * @return {@code false} + * @return {@code true} if the event was cancelled and this event bus is a + * {@linkplain CancellableEventBus cancellable event bus} * @apiNote This bus will always return {@code false} unless it is a * {@linkplain CancellableEventBus cancellable event bus}. * @see CancellableEventBus#post(Event) */ - @Contract(value = "_ -> false") boolean post(T event); /** @@ -136,7 +136,8 @@ public sealed interface EventBus permits CancellableEventBus, A boolean hasListeners(); /** - * Creates a new EventBus for the given event type on the {@linkplain BusGroup#DEFAULT default bus group}. + * Creates a new event bus for the given {@linkplain net.minecraftforge.eventbus.api.event event} type on the + * {@linkplain BusGroup#DEFAULT default bus group}. *

The returned EventBus must be stored in a {@code static final} field! Failing to do so will * severely hinder performance.

*

Additionally, there can only be one event bus instance per event type per bus group. If an event bus already @@ -152,7 +153,8 @@ static EventBus create(Class eventType) { } /** - * Creates a new event bus for the given event type on the given {@linkplain BusGroup bus group}. + * Creates a new event bus for the given {@linkplain net.minecraftforge.eventbus.api.event event} type on the given + * {@linkplain BusGroup bus group}. *

The returned event bus must be stored in a {@code static final} field! Failing to do so will * severely hinder performance.

*

Additionally, there can only be one event bus instance per event type per bus group. If an event bus already From 09ca1c986e978903ecb893135f9ce9dde968b8f7 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 22:47:09 -0400 Subject: [PATCH 09/11] JavaDocs - EventCharacteristic package --- .../eventbus/api/bus/CancellableEventBus.java | 6 ++++++ .../eventbus/api/bus/package-info.java | 5 +++++ .../api/event/characteristic/Cancellable.java | 11 ++++++++-- .../event/characteristic/MonitorAware.java | 20 +++++++++++++++--- .../event/characteristic/SelfDestructing.java | 9 +++++--- .../api/event/characteristic/SelfPosting.java | 21 ++++++++++++++++--- .../event/characteristic/package-info.java | 5 +++++ 7 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java b/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java index 1b8006d..a55cd39 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/CancellableEventBus.java @@ -29,6 +29,12 @@ * {@linkplain EventListener#alwaysCancelling() always cancel} the event. The specialized * {@link #addListener(Predicate)} method, along with its sisters in this class, are designed to give event listeners * that characteristic.

+ *

When a listener is registered, it is not invoked if the event has been cancelled. The only exception to this is + * for {@linkplain Priority#MONITOR monitoring} listeners, which are always invoked at the end of the event posting even + * if the event was cancelled. Monitoring listeners can be added using either the {@link Priority#MONITOR} priority or + * by registering an {@link ObjBooleanBiConsumer} in {@link #addListener(ObjBooleanBiConsumer)}. Monitoring + * listeners are forbidden from mutating events. This contract can be strengthened by the event author using + * the {@link net.minecraftforge.eventbus.api.event.characteristic.MonitorAware} characteristic.

* *

Example

*

Here is a small example that showcases the different listeners this type of event bus can have.

diff --git a/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java b/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java index 1eb8184..ad6166b 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java +++ b/src/main/java/net/minecraftforge/eventbus/api/bus/package-info.java @@ -2,6 +2,11 @@ * Copyright (c) Forge Development LLC * SPDX-License-Identifier: LGPL-2.1-only */ +/** + * EventBus is built around the idea of {@linkplain net.minecraftforge.eventbus.api.bus.EventBus event buses} that are + * grouped together by {@linkplain net.minecraftforge.eventbus.api.bus.BusGroup bus groups}. This package contains this + * core part of the API. + */ @NullMarked package net.minecraftforge.eventbus.api.bus; diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java index 4c97feb..d64f97d 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/Cancellable.java @@ -8,7 +8,14 @@ import net.minecraftforge.eventbus.internal.Event; /** - * A cancellable event returns {@code true} from {@link CancellableEventBus#post(Event)} if it was cancelled. - *

When an event is cancelled, it will not be passed to any further non-monitor listeners.

+ * The ability to cancel an event is one of the core functionalities of EventBus. This functionality is carried in to + * this iteration of the EventBus API with the introduction of the cancellable + * {@linkplain net.minecraftforge.eventbus.api.event.characteristic characteristic}. + *

A cancellable event returns {@code true} from {@link CancellableEventBus#post(Event)} if it was cancelled. When + * an event is cancelled, it will not be passed to any further + * non-{@linkplain net.minecraftforge.eventbus.api.listener.Priority#MONITOR monitor} listeners. For further details on + * a cancellable event's interactions with an event bus, see {@link CancellableEventBus}.

+ * + * @see CancellableEventBus */ public non-sealed interface Cancellable extends EventCharacteristic {} diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java index 83db404..b43eecb 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/MonitorAware.java @@ -10,15 +10,29 @@ import org.jetbrains.annotations.Contract; /** - * Events that are {@link MonitorAware} can provide stronger immutability guarantees to monitor listeners by returning - * unmodifiable views or throwing exceptions on mutation attempts when monitoring. - *

Only supported for {@link MutableEvent} at this time.

+ * Events that are {@linkplain net.minecraftforge.eventbus.api.listener.Priority#MONITOR monitor}-aware are able to + * provide stronger immutability guarantees to monitor listeners by returning unmodifiable views and ignoring (or + * throwing exceptions) on mutation attempts when monitoring. * + * @implNote This characteristic is only supported for {@link MutableEvent} at this time. If used on any other type that + * does not extend it, an {@link IllegalArgumentException} will be thrown when the + * {@linkplain net.minecraftforge.eventbus.api.bus.EventBus event bus} is created for it. * @apiNote This is an experimental feature! It may be removed, renamed or otherwise changed without * notice. */ @ApiStatus.Experimental public non-sealed interface MonitorAware extends EventCharacteristic { + /** + * Checks if this event is currently being + * {@linkplain net.minecraftforge.eventbus.api.listener.Priority#MONITOR monitored}. This can be used to provide + * stronger immutability guarantees as described by the documentation of {@link MonitorAware}. + * + * @return + * @implSpec This method + * {@linkplain org.jetbrains.annotations.ApiStatus.NonExtendable must not be overridden}! It uses + * internals of {@link MutableEvent} in order to determine the monitoring state of the event. If overridden, your + * event will be unable to determine if it is actually monitoring! + */ @Contract(pure = true) @ApiStatus.NonExtendable default boolean isMonitoring() { diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java index cfc141c..7ff2544 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfDestructing.java @@ -5,11 +5,14 @@ package net.minecraftforge.eventbus.api.event.characteristic; import net.minecraftforge.eventbus.api.bus.EventBus; -import net.minecraftforge.eventbus.internal.AbstractEventBusImpl; /** - * A self-destructing event will {@link AbstractEventBusImpl#dispose() dispose} of its associated {@link EventBus} - * after it has been posted to free up resources, after which it cannot be posted to again. + * A self-destructing event will {@linkplain net.minecraftforge.eventbus.api.bus.BusGroup#dispose() dispose} of its + * associated {@link EventBus} after it has been posted to free up resources. *

This is useful for single-use lifecycle events.

+ * + * @apiNote Similar to {@link net.minecraftforge.eventbus.api.bus.BusGroup#dispose()}, the posting of this event is a + * destructive action that will cause its resources to be freed. It must not be used after it is + * posted! */ public non-sealed interface SelfDestructing extends EventCharacteristic {} diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java index 03140d3..144f087 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/SelfPosting.java @@ -10,8 +10,8 @@ import org.jetbrains.annotations.Contract; /** - * Self-posting events are associated with a default {@link EventBus} in order to offer some convenience instance - * methods. + * Self-posting events are associated with a default {@linkplain EventBus event bus} in order to offer some convenient + * instance methods. * *

Example

* {@snippet : @@ -40,21 +40,36 @@ @ApiStatus.Experimental public non-sealed interface SelfPosting extends EventCharacteristic { /** - * @implSpec This should directly return a {@code static final} field without additional logic or processing. + * The default event bus for this event. It will be used by the {@link #post()} and {@link #fire()} methods. + * + * @return The default event bus for this event + * @implSpec This method must directly return a {@code static final} field without additional logic or processing. + * Failure to do so may result in performance hindrances. */ + @Contract(pure = true) EventBus getDefaultBus(); /** + * Posts this event to all listeners registered to its {@linkplain #getDefaultBus() default event bus}. + * + * @return {@code true} if the event was cancelled and this event bus is a + * {@linkplain net.minecraftforge.eventbus.api.bus.CancellableEventBus cancellable event bus} * @see EventBus#post(Event) */ + @ApiStatus.NonExtendable @SuppressWarnings("unchecked") default boolean post() { return getDefaultBus().post((T) this); } /** + * Fires this event to all listeners registered to its {@linkplain #getDefaultBus() default event bus}. + *

After posting, this event is returned from this method. It may be mutated.

+ * + * @return This event after being posted * @see EventBus#fire(Event) */ + @ApiStatus.NonExtendable @Contract(value = "-> this") @SuppressWarnings("unchecked") default T fire() { diff --git a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java index 37c81f0..5b279ba 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java +++ b/src/main/java/net/minecraftforge/eventbus/api/event/characteristic/package-info.java @@ -2,6 +2,11 @@ * Copyright (c) Forge Development LLC * SPDX-License-Identifier: LGPL-2.1-only */ +/** + * Events can have characteristics that define how they behave. These characterists are computed when the + * {@linkplain net.minecraftforge.eventbus.api.bus.EventBus event bus} for the specific even is created, and those + * characteristics are defined as interfaces which the event type can implement. + */ @NullMarked package net.minecraftforge.eventbus.api.event.characteristic; From 63253e03a4924330e9e6cd386e46c379772de0b5 Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 23:01:53 -0400 Subject: [PATCH 10/11] JavaDocs - EventListner (potentially incomplete) --- .../eventbus/api/listener/EventListener.java | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java b/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java index e7039a3..e491030 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java +++ b/src/main/java/net/minecraftforge/eventbus/api/listener/EventListener.java @@ -13,24 +13,41 @@ import java.util.function.Consumer; /** - * Users can retain instances of this interface to remove listeners that were previously added to the same - * {@link EventBus}.You can obtain instances of this interface by calling any of the {@code addListener} methods - * on an EventBus, such as {@link EventBus#addListener(Consumer)}. - *

Internally, this acts as a wrapper over lambdas to give them identity, enrich debug info and to allow - * various conversion operations to different lambda types.

+ * An event listener holds info about a lister that was registered in an {@linkplain EventBus event bus} or + * {@linkplain net.minecraftforge.eventbus.api.bus.BusGroup bus group}. Consumers should retain instances of this + * interface in order to remove listeners that were previously added to the same event bus. + * + * @implNote Internally, this acts as a wrapper over lambdas to give them identity, enrich debug info and to allow + * various conversion operations to different lambda types. */ public sealed interface EventListener permits EventListenerImpl { + /** + * The event type that this listener is listening for. + * + * @return The event type + */ @Contract(pure = true) @SuppressWarnings("ClassEscapesDefinedScope") // ? can be a subtype of Event which is publicly accessible Class eventType(); /** + * The priority of this listener. + *

This is used by the {@linkplain EventBus event bus} to determine the order in which to invoke listeners.

+ * + * @return The priority of this listener * @see Priority */ @Contract(pure = true) byte priority(); /** + * Whether this listener will always cancel a + * {@linkplain net.minecraftforge.eventbus.api.event.characteristic.Cancellable cancellable} event. + * This is almost always {@code false} unless the listener is created using + * {@link CancellableEventBus#addListener(boolean, Consumer)} or + * {@link CancellableEventBus#addListener(byte, boolean, Consumer)}. + * + * @return {@code true} if this listener is always cancelling * @see CancellableEventBus#addListener(boolean, Consumer) */ @Contract(pure = true) From f567132154b560d3ca14abe4fcbd24a45fb4a7dc Mon Sep 17 00:00:00 2001 From: Jonathing Date: Wed, 23 Apr 2025 23:02:03 -0400 Subject: [PATCH 11/11] JavaDocs - ObjBooleanBiConsumer --- .../eventbus/api/listener/ObjBooleanBiConsumer.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/minecraftforge/eventbus/api/listener/ObjBooleanBiConsumer.java b/src/main/java/net/minecraftforge/eventbus/api/listener/ObjBooleanBiConsumer.java index 5fba37c..0e9d060 100644 --- a/src/main/java/net/minecraftforge/eventbus/api/listener/ObjBooleanBiConsumer.java +++ b/src/main/java/net/minecraftforge/eventbus/api/listener/ObjBooleanBiConsumer.java @@ -7,9 +7,20 @@ import java.util.function.BiConsumer; /** - * A {@link BiConsumer} that takes an object and a primitive boolean, to avoid boxing. + * A {@linkplain BiConsumer bi-consumer} that accepts an object and a primitive boolean. + *

This is used over {@link BiConsumer}{@code <}{@link Object}, {@link Boolean}{@code >} to avoid + * boxing.

+ * + * @see BiConsumer */ @FunctionalInterface public interface ObjBooleanBiConsumer { + /** + * Performs this operation on the given arguments. + * + * @param obj The object + * @param bool The primitive boolean + * @see BiConsumer#accept(Object, Object) + */ void accept(T obj, boolean bool); }