Fast and modular event library for Java.
To use LambdaEvents with Gradle/Maven you can follow the instructions on maven central or my maven server.
repositories {
mavenCentral()
}
dependencies {
implementation "net.lenni0451:LambdaEvents:x.x.x"
}
<dependency>
<groupId>net.lenni0451</groupId>
<artifactId>LambdaEvents</artifactId>
<version>x.x.x</version>
</dependency>
You should check maven central or my maven server for the latest version.
You can download the jar file from my Jenkins server.
Since LambdaEvents has no dependencies you don't need to download any other jar files.
To use LambdaEvents you need to create an instance of the LambdaManager
class.
It is the main class of the library used to register and call events.
LambdaManager eventManager = LambdaManager.basic(generator);
Or for a thread safe version:
LambdaManager eventManager = LambdaManager.threadSafe(generator);
There is no global instance to prevent event conflicts.
Because of the dynamic nature of LambdaEvents you need to provide an IGenerator
implementation.
It is used to generate the caller which calls the event listener.
The following implementations are provided:
- ReflectionGenerator
- MethodHandleGenerator
- LambdaMetaFactoryGenerator
- ASMGenerator (Requires Reflect and ASM)
Check out the JMH Benchmark section for performance comparisons.
The MethodHandleGenerator
and the LambdaMetaFactoryGenerator
have an optional MethodHandles.Lookup
parameter.
To create your own implementation you need to implement the IGenerator
interface.
The generate
method is used to generate a caller for handler which take the event as a parameter.
The generateVirtual
method is used to generate a caller for handler which don't take the event as a parameter.
In LambdaEvents there is no need to implement an interface or extend a class to create an event.
You can pass any object as an event. Remember that the class is used to identify the event type. Inheritance is not checked and treated as a different event type.
LambdaEvents has 6 different ways to listen to events:
Type | Description |
---|---|
Static methods | A static method annotated with @EventHandler |
Virtual methods | A virtual/non-static method annotated with @EventHandler |
(static) Runnable field | A runnable field annotated with @EventHandler |
(static) Consumer field | A consumer field annotated with @EventHandler |
Independent Runnable | A runnable which is passed to the register method |
Independent Consumer | A consumer which is passed to the register method |
All register
/unregister
methods have an optional event class
parameter to specify the type of the event to register/unregister.
This parameter is required when registering an independent event handler.
Runnable fields or Consumer fields without type parameters require the event type(s) to be added to the @EventHandler(events = {Event.class})
annotation.
Methods are required to not have any parameters or to have the event type as a parameter. Methods without parameters require the event type(s) to be added to the @EventHandler(events = {Event.class})
annotation.
Independent Runnables or Consumers require the event type(s) to be passed to the register
method.
To register static event handler (methods and fields) you need to call the register
method of the LambdaManager
instance passing the owner class.
eventManager.register(Example.class);
To register virtual event handler (methods and fields) you need to call the register
method of the LambdaManager
instance passing the owner object.
eventManager.register(new Example());
To register independent event handler (runnables and consumers) you need to call the register
method of the LambdaManager
instance passing the handler, priority and event type(s).
//Runnable
Runnable handler = () -> System.out.println("called");
eventManager.register(handler, 0, Event.class);
//Consumer
Consumer<Event> handler = e -> System.out.println("called " + e);
eventManager.register(handler, 0, Event.class);
To unregister event handler you have to call the respective unregister
method of the LambdaManager
instance in the same way you registered them.
//Static
eventManager.unregister(Example.class);
//Virtual
eventManager.unregister(oldExampleInstance);
//Independent
eventManager.unregister(handler, Event.class);
You can also unregister all event handlers for a specific event type by calling the unregisterAll
method.
//Unregister all event handlers for the event type
//This includes static and virtual event handlers
eventManager.unregisterAll(Event.class);
//Unregister all event handler for the event type and the given class predicate
//In this case all handlers which extend AbstractVirtualHandler are unregistered
//This includes static and virtual event handlers
eventManager.unregisterAll(Event.class, AbstractVirtualHandler.class::isAssignableFrom);
//The unregisterAll method also has an optional boolean to only unregister static/virtual event handlers
eventManager.unregisterAll(event, predicate, true /*static*/);
To call an event you need to call the call
method of the LambdaManager
instance passing the event object.
eventManager.call(new Event());
LambdaEvents supports priorities for event handlers which is used to determine the execution order.
The higher the priority is, the earlier the event handler is called.
Since there is no requirement for an event to implement an interface or extend a class you have to implement the cancellation yourself.
Even though it's not a requirement, it is recommended to implement the ICancellableEvent
interface.
Example:
public class Event implements ICancellableEvent {
private boolean cancelled = false;
public boolean isCancelled() {
return this.cancelled;
}
public void setCancelled(final boolean cancelled) {
this.cancelled = cancelled;
}
}
public class Caller {
public static void callEvent(final LambdaManager eventManager) {
Event event = eventManager.call(new Event());
if (event.isCancelled()) {
return;
}
//Continue the method
//...
}
}
The ICancellableEvent
interface is used for determining if an event handler should handle a cancelled event.
The handler can specify this by adding handleCancelled
(default true) to the @EventHandler
annotation.
Example:
public class Handler {
@EventHandler
public void handleEvent(final Event event) {
//This handler will be called even if the event is cancelled
}
@EventHandler(handleCancelled = false)
public void handleEvent(final Event event) {
//This handler will not be called if the event is cancelled
}
}
To cancel the event call chain and prevent following event handlers from being executed you can throw the StopCall.INSTANCE
exception.
If an exception is thrown during the registration process, the exception will be thrown to the caller.
If an exception is thrown by an event handler, the ExceptionHandler
of the LambdaManager
instance will be called.
It receives the handler, the event and the thrown exception as parameters.
By default the ExceptionHandler
will print the stack trace of the exception to the console (System.err
).
To make sure the LambdaManager
only registers only the correct event types you can use the IEventFilter
.
It is called with the event type and a type from where the filter was called.
The filter can return true
to allow the registration/calling of the event or false
to skip it. An exception can be thrown to notify the caller that the event type is not allowed.
Check types:
Type | Description |
---|---|
CALL | The filter was called from the call or the callParents method |
REGISTER | The filter was called from the register method without an explicit event type (wildcard registration) |
EXPLICIT_REGISTER | The filter was called from the register method with an explicitly specified event type |
The unregister
method does not call the filter.
Example filter:
public boolean check(final Class event, final CheckType checkType) {
if (event instanceof EventBase) return true;
if (CheckType.CALL.equals(checkType)) throw new IllegalArgumentException();
if (CheckType.EXPLICIT_REGISTER.equals(checkType)) throw new IllegalArgumentException();
return false;
}
This filter will allow any event which extends EventBase
.
If an invalid event is passed to the call
method an IllegalArgumentException
will be thrown.
If an event is registered explicitly an IllegalArgumentException
will be thrown.
Any other event will be blocked without throwing an exception (silent block).
The Benchmark shows the average time it takes to call an event 100_000 times.
The lower the time is, the better the call performance of the generator is.
The tests were run using Java 17 and may vary on other Java versions.
Benchmark | Mode | Cnt | Score | Error | Units |
---|---|---|---|---|---|
CallBenchmark.callASM | avgt | 4 | 1253339,286 | 81976,516 | ns/op |
CallBenchmark.callLambdaMetaFactory | avgt | 4 | 1270657,312 | 133794,400 | ns/op |
CallBenchmark.callMethodHandles | avgt | 4 | 1893870,724 | 569223,318 | ns/op |
CallBenchmark.callReflection | avgt | 4 | 1466385,654 | 256169,146 | ns/op |