Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ eventBus.subscribe(new AsyncChatListener());

eventBus.call(new ChatMessageEvent("Chat message"));
```
Registering custom subscription annotation resolver
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MySubscribe {
int priority() default 0;
boolean async() default false;
boolean handleCancelled() default true;
}

eventBus.registerAnnotationResolver(
SubscriptionAnnotationResolver.of(
MySubscribe.class,
annotation -> new SubscriptionAnnotationInfo(
annotation.priority(),
annotation.async(),
annotation.handleCancelled()
)
)
);

eventBus.removeAnnotationResolver(MySubscribe.class);
```
Subscribing to events using the `@Subscribe` annotation
```java
public class AsyncChatListener {
Expand Down Expand Up @@ -128,4 +151,4 @@ implementation "org.densy.eventbus:core:1.0.0-SNAPSHOT"
___

### Special Thanks
Inspired by the [AllayMC event system](https://github.com/AllayMC/Allay/tree/master/api/src/main/java/org/allaymc/api/eventbus), I took some code from here.
Inspired by the [AllayMC event system](https://github.com/AllayMC/Allay/tree/master/api/src/main/java/org/allaymc/api/eventbus), I took some code from here.
23 changes: 22 additions & 1 deletion api/src/main/java/org/densy/eventbus/api/EventBus.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.densy.eventbus.api.subscription.Subscriber;
import org.densy.eventbus.api.subscription.annotation.Subscribe;
import org.densy.eventbus.api.subscription.annotation.SubscriptionAnnotationResolver;
import org.jetbrains.annotations.UnmodifiableView;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
Expand All @@ -13,6 +15,25 @@
*/
public interface EventBus {

/**
* Registers a resolver that maps an annotation to subscription execution settings.
* <p>
* Registered resolvers are used when scanning listener methods in {@link #subscribe(Object)}.
*
* @param resolver annotation resolver
* @param <A> annotation type
*/
<A extends Annotation> void registerAnnotationResolver(SubscriptionAnnotationResolver<A> resolver);

/**
* Unregisters an annotation resolver by annotation type.
*
* @param annotationType annotation class whose resolver should be removed
* @param <A> annotation type
* @return true if a resolver was removed, false otherwise
*/
<A extends Annotation> boolean removeAnnotationResolver(Class<A> annotationType);

/**
* Retrieves all registered subscribers.
*
Expand Down Expand Up @@ -143,4 +164,4 @@ public interface EventBus {
* @return the async executor service
*/
ExecutorService getAsyncExecutor();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.densy.eventbus.api.subscription.annotation;

/**
* Execution options resolved from an event subscription annotation.
*
* @param priority handler execution priority, higher values are executed first
* @param async whether the handler should execute asynchronously
* @param handleCancelled whether the handler should process cancelled events
*/
public record SubscriptionAnnotationInfo(int priority, boolean async, boolean handleCancelled) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.densy.eventbus.api.subscription.annotation;

import lombok.Getter;

import java.lang.annotation.Annotation;
import java.util.Objects;
import java.util.function.Function;

/**
* Resolves event subscription options from an annotation instance.
*
* @param <A> annotation type
*/
@Getter
public final class SubscriptionAnnotationResolver<A extends Annotation> {

private final Class<A> annotationType;
private final Function<A, SubscriptionAnnotationInfo> mapper;

private SubscriptionAnnotationResolver(Class<A> annotationType, Function<A, SubscriptionAnnotationInfo> mapper) {
this.annotationType = Objects.requireNonNull(annotationType, "annotationType");
this.mapper = Objects.requireNonNull(mapper, "mapper");
}

/**
* Creates a typed resolver for a subscription annotation.
*
* @param annotationType annotation class to resolve
* @param mapper mapper that extracts subscription settings from annotation
* @param <A> annotation type
* @return resolver instance
*/
public static <A extends Annotation> SubscriptionAnnotationResolver<A> of(
Class<A> annotationType,
Function<A, SubscriptionAnnotationInfo> mapper
) {
return new SubscriptionAnnotationResolver<>(annotationType, mapper);
}

/**
* Resolves subscription options from annotation.
*
* @param annotation annotation instance
* @return resolved subscription options
*/
public SubscriptionAnnotationInfo resolve(A annotation) {
return Objects.requireNonNull(mapper.apply(annotation), "mapper result");
}
}
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ java {

allprojects {
group = "org.densy.eventbus"
version = "1.0.0-SNAPSHOT"
version = "1.1.0-SNAPSHOT"
}

subprojects {
Expand Down
65 changes: 59 additions & 6 deletions core/src/main/java/org/densy/eventbus/core/EventBusImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
import org.densy.eventbus.api.exception.EventException;
import org.densy.eventbus.api.subscription.Subscriber;
import org.densy.eventbus.api.subscription.annotation.Subscribe;
import org.densy.eventbus.api.subscription.annotation.SubscriptionAnnotationInfo;
import org.densy.eventbus.api.subscription.annotation.SubscriptionAnnotationResolver;
import org.densy.eventbus.core.subscription.LambdaSubscriber;
import org.densy.eventbus.core.subscription.MethodSubscriber;
import org.densy.eventbus.core.util.Utils;
import org.jetbrains.annotations.UnmodifiableView;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -24,6 +27,7 @@ public class EventBusImpl implements EventBus {
private final Map<Class<? extends Event>, List<Subscriber<?>>> subscribersByEvent = new ConcurrentHashMap<>();
private final Map<Object, List<Subscriber<?>>> subscribersByListener = new ConcurrentHashMap<>();
private final Map<Class<? extends Event>, Set<Class<? extends Event>>> eventParentsCache = new ConcurrentHashMap<>();
private final Map<Class<? extends Annotation>, SubscriptionAnnotationResolver<?>> annotationResolvers = new ConcurrentHashMap<>();
private final ExecutorService asyncExecutor;

private static final Comparator<Subscriber<?>> PRIORITY_COMPARATOR = (s1, s2) -> Integer.compare(s2.getPriority(), s1.getPriority());
Expand All @@ -39,6 +43,26 @@ public EventBusImpl() {

public EventBusImpl(ExecutorService asyncExecutor) {
this.asyncExecutor = asyncExecutor;
this.registerAnnotationResolver(SubscriptionAnnotationResolver.of(
Subscribe.class,
annotation -> new SubscriptionAnnotationInfo(
annotation.priority(),
annotation.async(),
annotation.handleCancelled()
)
));
}

@Override
public <A extends Annotation> void registerAnnotationResolver(SubscriptionAnnotationResolver<A> resolver) {
Objects.requireNonNull(resolver, "resolver");
annotationResolvers.put(resolver.getAnnotationType(), resolver);
}

@Override
public <A extends Annotation> boolean removeAnnotationResolver(Class<A> annotationType) {
Objects.requireNonNull(annotationType, "annotationType");
return annotationResolvers.remove(annotationType) != null;
}

@Override
Expand All @@ -64,9 +88,8 @@ public void subscribe(Object listener) {

for (Class<?> type = listener.getClass(); type != null; type = type.getSuperclass()) {
for (Method method : type.getDeclaredMethods()) {
Subscribe annotation = method.getAnnotation(Subscribe.class);

if (annotation == null) {
SubscriptionAnnotationInfo resolvedAnnotation = resolveSubscriptionAnnotation(method);
if (resolvedAnnotation == null) {
continue;
}

Expand All @@ -88,9 +111,9 @@ public void subscribe(Object listener) {

Subscriber<Event> subscriber = new MethodSubscriber<>(
eventClass,
annotation.priority(),
annotation.handleCancelled(),
annotation.async(),
resolvedAnnotation.priority(),
resolvedAnnotation.handleCancelled(),
resolvedAnnotation.async(),
method,
listener,
asyncExecutor
Expand Down Expand Up @@ -222,4 +245,34 @@ private CallResult callInternal(Event event, List<Subscriber<?>> subscribers) {
public ExecutorService getAsyncExecutor() {
return asyncExecutor;
}

@SuppressWarnings("unchecked")
private SubscriptionAnnotationInfo resolveSubscriptionAnnotation(Method method) {
Class<? extends Annotation> resolvedType = null;
Annotation resolvedAnnotation = null;
SubscriptionAnnotationResolver<?> resolvedResolver = null;

for (Map.Entry<Class<? extends Annotation>, SubscriptionAnnotationResolver<?>> entry : annotationResolvers.entrySet()) {
Class<? extends Annotation> annotationType = entry.getKey();
Annotation annotation = method.getAnnotation(annotationType);
if (annotation == null) {
continue;
}

if (resolvedAnnotation != null) {
throw new EventException("Method " + method + " has multiple supported subscription annotations: " + resolvedType.getSimpleName() + ", " + annotationType.getSimpleName());
}

resolvedType = annotationType;
resolvedAnnotation = annotation;
resolvedResolver = entry.getValue();
}

if (resolvedAnnotation == null) {
return null;
}

SubscriptionAnnotationResolver<Annotation> typedResolver = (SubscriptionAnnotationResolver<Annotation>) resolvedResolver;
return typedResolver.resolve(resolvedAnnotation);
}
}
Loading