diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenersManager.java b/API/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenersManager.java index 523df3e..05df523 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenersManager.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenersManager.java @@ -25,9 +25,7 @@ package io.github.jwdeveloper.tiktok.listener; import java.util.List; /** - * You can dynamically add or removing TikTokEventListener - * - * + * Manage events listeners objects */ public interface ListenersManager { diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java index c28cd27..cccaef2 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java @@ -128,11 +128,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { dependance.registerSingleton(LiveMessagesHandler.class, TikTokLiveMessageHandler.class); //listeners - dependance.registerSingleton(ListenersManager.class, container -> - { - var eventHandlers = (LiveEventsHandler) container.find(LiveEventsHandler.class); - return new TikTokListenersManager(listeners, eventHandlers); - }); + dependance.registerSingleton(ListenersManager.class, TikTokListenersManager.class); //networking dependance.registerSingleton(HttpClientFactory.class); @@ -182,6 +178,10 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { onCustomDependencies.forEach(action -> action.accept(dependance)); var container = dependance.build(); + + var listenerManager = container.find(ListenersManager.class); + listeners.forEach(listenerManager::addListener); + return container.find(LiveClient.class); } diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenerBindingModel.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenerMethodInfo.java similarity index 78% rename from Client/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenerBindingModel.java rename to Client/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenerMethodInfo.java index a8394e9..7b9f085 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenerBindingModel.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/ListenerMethodInfo.java @@ -22,18 +22,26 @@ */ package io.github.jwdeveloper.tiktok.listener; +import io.github.jwdeveloper.tiktok.annotations.Priority; + import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; +import lombok.Data; -import lombok.Value; - -import java.util.List; -import java.util.Map; +import java.lang.reflect.Method; -@Value -public class ListenerBindingModel -{ - Object listener; +@Data +public class ListenerMethodInfo { + private Object listener; - Map, List>> events; + private Class eventType; + + private Method method; + + private Priority priority; + + private boolean async; + + private EventConsumer action = (a, b) -> { + }; } diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManager.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManager.java index 03534e1..50855d2 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManager.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManager.java @@ -23,6 +23,7 @@ package io.github.jwdeveloper.tiktok.listener; +import io.github.jwdeveloper.dependance.api.DependanceContainer; import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.exceptions.TikTokEventListenerMethodException; @@ -31,108 +32,127 @@ import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveEventsHandler; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; +import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; public class TikTokListenersManager implements ListenersManager { - private final LiveEventsHandler eventObserver; - private final List bindingModels; - private final ExecutorService executorService; - public TikTokListenersManager(List listeners, LiveEventsHandler tikTokEventHandler) { + private final Map> listeners; + private final LiveEventsHandler eventObserver; + private final ExecutorService executorService; + private final DependanceContainer dependanceContainer; + + + public TikTokListenersManager(LiveEventsHandler tikTokEventHandler, + DependanceContainer dependanceContainer) { this.eventObserver = tikTokEventHandler; - this.bindingModels = new ArrayList<>(listeners.size()); - for (var listener : listeners) { - addListener(listener); - } - executorService = Executors.newFixedThreadPool(4); + this.dependanceContainer = dependanceContainer; + this.listeners = new HashMap<>(); + executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); } @Override public List getListeners() { - return bindingModels.stream().map(ListenerBindingModel::getListener).toList(); + return listeners.keySet().stream().toList(); } @Override public void addListener(Object listener) { - var alreadyExists = bindingModels.stream().filter(e -> e.getListener() == listener).findAny(); - if (alreadyExists.isPresent()) { + if (listeners.containsKey(listener)) { throw new TikTokLiveException("Listener " + listener.getClass() + " has already been registered"); } - var bindingModel = bindToEvents(listener); - for (var eventEntrySet : bindingModel.getEvents().entrySet()) { - var eventType = eventEntrySet.getKey(); - for (var methods : eventEntrySet.getValue()) { - eventObserver.subscribe(eventType, methods); - } + var methodsInfo = getMethodsInfo(listener); + for (var methodInfo : methodsInfo) { + eventObserver.subscribe(methodInfo.getEventType(), methodInfo.getAction()); } - bindingModels.add(bindingModel); + listeners.put(listener, methodsInfo); } @Override public void removeListener(Object listener) { - var optional = bindingModels.stream().filter(e -> e.getListener() == listener).findAny(); - if (optional.isEmpty()) { + if (!listeners.containsKey(listener)) { return; } - - var bindingModel = optional.get(); - - for (var eventEntrySet : bindingModel.getEvents().entrySet()) { - var eventType = eventEntrySet.getKey(); - for (var methods : eventEntrySet.getValue()) { - eventObserver.unsubscribe(eventType, methods); - } + var methodsInfo = listeners.get(listener); + for (var methodInfo : methodsInfo) { + eventObserver.unsubscribe(methodInfo.getEventType(), methodInfo.getAction()); } - bindingModels.remove(optional.get()); + listeners.remove(listener); } - private ListenerBindingModel bindToEvents(Object listener) { - var clazz = listener.getClass(); - var methods = Arrays.stream(clazz.getDeclaredMethods()) - .filter(m -> - m.getParameterCount() >= 1 && - m.isAnnotationPresent(TikTokEventObserver.class)) + private List getMethodsInfo(Object listener) { + return Arrays.stream(listener.getClass().getDeclaredMethods()) + .filter(e -> e.isAnnotationPresent(TikTokEventObserver.class)) + .filter(e -> e.getParameterCount() >= 1) + .map(method -> getSingleMethodInfo(listener, method)) + .sorted(Comparator.comparingInt(a -> a.getEventType().getName().length())) + .sorted(Comparator.comparingInt(a -> a.getPriority().priorityValue)) .toList(); - var eventsMap = new HashMap, List>>(); - for (var method : methods) { - var annotation = method.getAnnotation(TikTokEventObserver.class); - var tiktokEventsParameters = Arrays.stream(method.getParameters()) - .filter(parameter -> - TikTokEvent.class.isAssignableFrom(parameter.getType()) || - parameter.getType().equals(TikTokEvent.class)) - .toList(); - if (tiktokEventsParameters.size() != 1) { - throw new TikTokEventListenerMethodException("Method " + method.getName() + "() must have only one parameter that inherits from class " + TikTokEvent.class.getName()); - } + } - var eventType = tiktokEventsParameters.get(0).getType(); - EventConsumer eventMethodRef = (liveClient, event) -> + private ListenerMethodInfo getSingleMethodInfo(Object listener, Method method) { + + method.setAccessible(true); + var annotation = method.getAnnotation(TikTokEventObserver.class); + var tiktokEventType = Arrays.stream(method.getParameterTypes()) + .filter(TikTokEvent.class::isAssignableFrom) + .findFirst() + .orElseThrow(() -> new TikTokEventListenerMethodException("Method " + method.getName() + "() must have only one parameter that inherits from class " + TikTokEvent.class.getName())); + + var info = new ListenerMethodInfo(); + info.setListener(listener); + info.setAsync(annotation.async()); + info.setPriority(annotation.priority()); + info.setEventType(tiktokEventType); + info.setAction(createAction(listener, method, tiktokEventType)); + + if (info.isAsync()) { + var action = info.getAction(); + info.setAction((liveClient, event) -> { - if (annotation.async()) { - executorService.submit(() -> - { - try { - method.setAccessible(true); - method.invoke(listener, liveClient, event); - } catch (Exception e) { - throw new TikTokEventListenerMethodException(e); - } - }); - return; - } - - try { - method.setAccessible(true); - method.invoke(listener, liveClient, event); - } catch (Exception e) { - throw new TikTokEventListenerMethodException(e); - } - }; - eventsMap.computeIfAbsent(eventType, (a) -> new ArrayList<>()).add(eventMethodRef); + executorService.submit(() -> + { + action.onEvent(liveClient, event); + }); + }); } - return new ListenerBindingModel(listener, eventsMap); + return info; + } + + + //I know, implementation of this might look complicated + private EventConsumer createAction(Object listener, Method method, Class tiktokEventType) { + AtomicReference eventObjectRef = new AtomicReference<>(); + var methodContainer = dependanceContainer.createChildContainer() + .configure(configuration -> + { + //Modfying container, so it returns TikTokEvent object instance, + //when TikTokEvent type is encountered in the methods parameters + configuration.onInjection(injectionEvent -> + { + if (injectionEvent.input().isAssignableFrom(tiktokEventType)) { + return eventObjectRef.get(); + } + return injectionEvent.output(); + }); + }) + .build(); + + return (liveClient, event) -> + { + try { + eventObjectRef.set(event); + //Creating list of input objects based on method parameters + //Objects are received from container + var parameters = methodContainer.resolveParameters(method); + method.invoke(listener, parameters); + } catch (Exception e) { + throw new TikTokEventListenerMethodException(e); + } + }; } } \ No newline at end of file diff --git a/Client/src/test/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManagerTest.java b/Client/src/test/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManagerTest.java index 4e5cd97..915bcfe 100644 --- a/Client/src/test/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManagerTest.java +++ b/Client/src/test/java/io/github/jwdeveloper/tiktok/listener/TikTokListenersManagerTest.java @@ -22,6 +22,8 @@ */ package io.github.jwdeveloper.tiktok.listener; +import io.github.jwdeveloper.dependance.Dependance; +import io.github.jwdeveloper.dependance.api.DependanceContainer; import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler; import io.github.jwdeveloper.tiktok.annotations.Priority; import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver; @@ -45,17 +47,24 @@ class TikTokListenersManagerTest { private TikTokLiveEventHandler eventObserver; private TikTokListenersManager tikTokListenersManager; + private DependanceContainer dependanceContainer; + private LiveClient liveClient; @BeforeEach void setUp() { - eventObserver = Mockito.mock(TikTokLiveEventHandler.class); - List listeners = new ArrayList<>(); - tikTokListenersManager = new TikTokListenersManager(listeners, eventObserver); + + liveClient = Mockito.mock(LiveClient.class); + eventObserver = new TikTokLiveEventHandler(); + + dependanceContainer = Dependance.newContainer() + .registerSingleton(LiveClient.class, liveClient) + .build(); + tikTokListenersManager = new TikTokListenersManager(eventObserver, dependanceContainer); } @Test void addListener() { - Object listener =new TikTokEventListenerTest(); + Object listener = new TikTokEventListenerTest(); tikTokListenersManager.addListener(listener); List listeners = tikTokListenersManager.getListeners(); @@ -85,6 +94,17 @@ class TikTokListenersManagerTest { assertTrue(listeners.isEmpty()); } + @Test + public void shouldTriggerEvents() { + + Object listener = new TikTokEventListenerTest(); + tikTokListenersManager.addListener(listener); + + + var fakeGiftEvent = TikTokGiftEvent.of("TestRosa", 1, 1); + eventObserver.publish(liveClient, fakeGiftEvent); + } + @Test void removeListener_notRegistered_doesNotThrow() { Object listener = new TikTokEventListenerTest(); @@ -92,24 +112,30 @@ class TikTokListenersManagerTest { } - public static class TikTokEventListenerTest - { + public static class TikTokEventListenerTest { @TikTokEventObserver - public void onJoin(LiveClient client, TikTokJoinEvent joinEvent) - { - + public void onJoin(LiveClient client, TikTokJoinEvent joinEvent) { + System.out.println("Hello from on join" + client + " " + joinEvent); } - @TikTokEventObserver(priority = Priority.NORMAL,async=true) - public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent) - { - + @TikTokEventObserver(priority = Priority.LOWEST) + public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent) { + System.out.println("Hello from onGift lowest priority" + client + " " + giftMessageEvent); } - @TikTokEventObserver - public void onEvent(LiveClient client, TikTokEvent event) - { + @TikTokEventObserver(priority = Priority.NORMAL) + public void onGift2(LiveClient client, TikTokGiftEvent giftMessageEvent) { + System.out.println("Hello from onGift normal priority " + client + " " + giftMessageEvent); + } + @TikTokEventObserver(priority = Priority.HIGHEST) + public void onGift3(LiveClient client, TikTokGiftEvent giftMessageEvent) { + System.out.println("Hello from onGift highest priority " + client + " " + giftMessageEvent); + } + + @TikTokEventObserver(async = true) + public void onEvent(LiveClient client, TikTokEvent event) { + System.out.println("Hello from onEvent im running on the thread " + Thread.currentThread().getName()); } } } \ No newline at end of file