diff --git a/src/main/java/dev/zontreck/eventsbus/Bus.java b/src/main/java/dev/zontreck/eventsbus/Bus.java index fcf8cbd..a57ee68 100644 --- a/src/main/java/dev/zontreck/eventsbus/Bus.java +++ b/src/main/java/dev/zontreck/eventsbus/Bus.java @@ -1,14 +1,22 @@ package dev.zontreck.eventsbus; +import dev.zontreck.eventsbus.annotations.Priority; +import dev.zontreck.eventsbus.annotations.SingleshotEvent; +import dev.zontreck.eventsbus.annotations.Subscribe; import dev.zontreck.eventsbus.events.EventBusReadyEvent; import dev.zontreck.eventsbus.events.ResetEventBusEvent; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.stream.Collectors; +/** + * To be removed + *
+ * Use {} instead + */ +@Deprecated() public class Bus { /** * The main event bus! diff --git a/src/main/java/dev/zontreck/eventsbus/ClassScanner.java b/src/main/java/dev/zontreck/eventsbus/ClassScanner.java new file mode 100644 index 0000000..03c88e9 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/ClassScanner.java @@ -0,0 +1,93 @@ +package dev.zontreck.eventsbus; + + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Used internally. Do not directly invoke + *
+ * Accessor is set Protected intentionally!!!!!!!!! + */ +class ClassScanner { + /** + * Start the process of scanning the classes and forcing them to load in + *
+ * This is used by the event dispatcher + */ + protected static void DoScan() { + // Scan all classes in the classpath + Set> scannedClasses = scanClasses(); + + // Force loading of all scanned classes + for (Class clazz : scannedClasses) { + try { + // Load the class + Class.forName(clazz.getName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + + private static Set> scanClasses() { + String classpath = System.getProperty("java.class.path"); + String[] classpathEntries = classpath.split(File.pathSeparator); + + Set> scannedClasses = new HashSet<>(); + for (String classpathEntry : classpathEntries) { + File file = new File(classpathEntry); + if (file.isDirectory()) { + scanClassesInDirectory(file, "", scannedClasses); + } else if (file.isFile() && classpathEntry.endsWith(".jar")) { + scanClassesInJar(file, scannedClasses); + } + } + + return scannedClasses; + } + + private static void scanClassesInDirectory(File directory, String packageName, Set> scannedClasses) { + File[] files = directory.listFiles(); + if (files == null) { + return; + } + + for (File file : files) { + if (file.isDirectory()) { + scanClassesInDirectory(file, packageName + "." + file.getName(), scannedClasses); + } else if (file.getName().endsWith(".class")) { + String className = packageName + "." + file.getName().substring(0, file.getName().length() - 6); + try { + Class clazz = Class.forName(className); + scannedClasses.add(clazz); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } + + private static void scanClassesInJar(File jarFile, Set> scannedClasses) { + try (JarFile jf = new JarFile(jarFile)) { + for (JarEntry entry : Collections.list(jf.entries())) { + if (entry.getName().endsWith(".class")) { + String className = entry.getName().replace("/", ".").substring(0, entry.getName().length() - 6); + try { + Class clazz = Class.forName(className); + scannedClasses.add(clazz); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/dev/zontreck/eventsbus/Event.java b/src/main/java/dev/zontreck/eventsbus/Event.java index 3c520de..1053644 100644 --- a/src/main/java/dev/zontreck/eventsbus/Event.java +++ b/src/main/java/dev/zontreck/eventsbus/Event.java @@ -1,6 +1,9 @@ package dev.zontreck.eventsbus; +import dev.zontreck.eventsbus.annotations.Cancellable; +import dev.zontreck.eventsbus.annotations.Priority; + public class Event { private boolean cancelled = false; diff --git a/src/main/java/dev/zontreck/eventsbus/EventDispatcher.java b/src/main/java/dev/zontreck/eventsbus/EventDispatcher.java new file mode 100644 index 0000000..8cc210f --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/EventDispatcher.java @@ -0,0 +1,145 @@ +package dev.zontreck.eventsbus; + +import dev.zontreck.eventsbus.annotations.EventSubscriber; +import dev.zontreck.eventsbus.annotations.Priority; +import dev.zontreck.eventsbus.annotations.SingleshotEvent; +import dev.zontreck.eventsbus.annotations.Subscribe; +import dev.zontreck.eventsbus.events.EventBusReadyEvent; +import dev.zontreck.eventsbus.events.ResetEventBusEvent; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; + +public class EventDispatcher +{ + private static List singleshot = new ArrayList<>(); + private static List> subscribers = new ArrayList<>(); + + /** + * Scans every Java class that is currently loaded. It then checks for Subscribe, and a proper parameter before posting the Event. + * The Event will only be posted if not cancelled using {@link Event#setCancelled(boolean)} and that {@link Subscribe#allowCancelled()} allows. + * @param event The event to post + * @return True if cancelled. + */ + + public static boolean Post(Event event) + { + for(PriorityLevel level : PriorityLevel.values()) + { + + for(Class clazz : subscribers) + { + for(Method M :clazz.getMethods()) + { + if(!M.isAnnotationPresent(Subscribe.class)) continue; + + Subscribe subscriber = M.getAnnotation(Subscribe.class); + + + boolean canPost=true; + Class param = M.getParameterTypes()[0]; + if(param == event.getClass()) + { + if(M.isAnnotationPresent(SingleshotEvent.class)) + { + if(singleshot.contains(M)) + { + canPost=false; + } + } + } else canPost=false; + + PriorityLevel eventPriotityLevel= PriorityLevel.HIGH; // Default + + if(M.isAnnotationPresent(Priority.class)) + { + Priority prio = M.getAnnotation(Priority.class); + eventPriotityLevel=prio.Level(); + } + + if(level != eventPriotityLevel) + { + canPost=false; + } + + + // Dispatch the event now + + try { + if(event.isCancelled() && !subscriber.allowCancelled()) + continue; + else + M.invoke(null, event); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + + } + } + } + + return event.isCancelled(); + } + + + /** + * Scan all event subscribers + */ + public static void Scan() + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Package[] packages = Package.getPackages(); + + List> loaded = new ArrayList<>(); + + for(Package pkg : packages) + { + try{ + String packageName = pkg.getName(); + Field classesField = ClassLoader.class.getDeclaredField("classes"); + classesField.setAccessible(true); + + List> classes = (List>) classesField.get(classLoader); + + for(Class clazz : classes) + { + if(clazz.getPackage().getName().equalsIgnoreCase(packageName)) + { + + if(clazz.isAnnotationPresent(EventSubscriber.class)) + loaded.add(clazz); + } + } + + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + subscribers = loaded; + } + + /** + * Resets the events system. + *
+ * This action clears the Singleshot list for the events that should only be invoked once. And rescans all classes incase new classes were dynamically loaded. + */ + public static void Reset() + { + Post(new ResetEventBusEvent()); + + singleshot.clear(); + Scan(); + ClassScanner.DoScan(); + + Post(new EventBusReadyEvent()); + } +} diff --git a/src/main/java/dev/zontreck/eventsbus/Cancellable.java b/src/main/java/dev/zontreck/eventsbus/annotations/Cancellable.java similarity index 86% rename from src/main/java/dev/zontreck/eventsbus/Cancellable.java rename to src/main/java/dev/zontreck/eventsbus/annotations/Cancellable.java index a653b8d..34be386 100644 --- a/src/main/java/dev/zontreck/eventsbus/Cancellable.java +++ b/src/main/java/dev/zontreck/eventsbus/annotations/Cancellable.java @@ -1,4 +1,4 @@ -package dev.zontreck.eventsbus; +package dev.zontreck.eventsbus.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/dev/zontreck/eventsbus/Subscribe.java b/src/main/java/dev/zontreck/eventsbus/annotations/EventSubscriber.java similarity index 64% rename from src/main/java/dev/zontreck/eventsbus/Subscribe.java rename to src/main/java/dev/zontreck/eventsbus/annotations/EventSubscriber.java index c25ef17..0ee5023 100644 --- a/src/main/java/dev/zontreck/eventsbus/Subscribe.java +++ b/src/main/java/dev/zontreck/eventsbus/annotations/EventSubscriber.java @@ -1,4 +1,4 @@ -package dev.zontreck.eventsbus; +package dev.zontreck.eventsbus.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -6,7 +6,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(value = RetentionPolicy.RUNTIME) -@Target(value = ElementType.METHOD) - -public @interface Subscribe { +@Target(value = ElementType.TYPE) +public @interface EventSubscriber { } diff --git a/src/main/java/dev/zontreck/eventsbus/Priority.java b/src/main/java/dev/zontreck/eventsbus/annotations/Priority.java similarity index 76% rename from src/main/java/dev/zontreck/eventsbus/Priority.java rename to src/main/java/dev/zontreck/eventsbus/annotations/Priority.java index d25cd3e..88a4276 100644 --- a/src/main/java/dev/zontreck/eventsbus/Priority.java +++ b/src/main/java/dev/zontreck/eventsbus/annotations/Priority.java @@ -1,4 +1,6 @@ -package dev.zontreck.eventsbus; +package dev.zontreck.eventsbus.annotations; + +import dev.zontreck.eventsbus.PriorityLevel; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/dev/zontreck/eventsbus/SingleshotEvent.java b/src/main/java/dev/zontreck/eventsbus/annotations/SingleshotEvent.java similarity index 86% rename from src/main/java/dev/zontreck/eventsbus/SingleshotEvent.java rename to src/main/java/dev/zontreck/eventsbus/annotations/SingleshotEvent.java index 46144fe..6e8d612 100644 --- a/src/main/java/dev/zontreck/eventsbus/SingleshotEvent.java +++ b/src/main/java/dev/zontreck/eventsbus/annotations/SingleshotEvent.java @@ -1,4 +1,4 @@ -package dev.zontreck.eventsbus; +package dev.zontreck.eventsbus.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/dev/zontreck/eventsbus/annotations/Subscribe.java b/src/main/java/dev/zontreck/eventsbus/annotations/Subscribe.java new file mode 100644 index 0000000..9facb6e --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/annotations/Subscribe.java @@ -0,0 +1,18 @@ +package dev.zontreck.eventsbus.annotations; + +import dev.zontreck.eventsbus.Event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) + +public @interface Subscribe { + /** + * Marks that the subscribed method will not receive the signal if the event was cancelled with {@link Event#setCancelled(boolean)} + */ + boolean allowCancelled(); +} diff --git a/src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java b/src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java index 7645ac2..20f2e74 100644 --- a/src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java +++ b/src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java @@ -1,6 +1,6 @@ package dev.zontreck.eventsbus.events; -import dev.zontreck.eventsbus.Cancellable; +import dev.zontreck.eventsbus.annotations.Cancellable; import dev.zontreck.eventsbus.Event; /**