All Downloads are FREE. Search and download functionalities are using the official Maven repository.

wf.bukkit.context.BukkitContext Maven / Gradle / Ivy

package wf.bukkit.context;

import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import wf.bukkit.context.annotations.Autowired;
import wf.bukkit.context.annotations.BukkitConfiguration;
import wf.bukkit.context.annotations.Component;
import wf.bukkit.context.annotations.Init;
import wf.bukkit.context.depeneds.config.ConfigLoader;
import wf.bukkit.context.depeneds.config.annotation.Config;
import wf.bukkit.context.utils.ClassUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

public class BukkitContext {

    private final List> classes = new ArrayList<>();
    private final List> sortedClasses = new ArrayList<>();
    private final Map, Object> beans = new HashMap<>();
    private final Map, List>> dependencyGraph = new HashMap<>();

    private final BukkitConfiguration bukkitConfiguration;

    public static BukkitContext run(JavaPlugin plugin) {
        return new BukkitContext(plugin);
    }

    private BukkitContext(JavaPlugin plugin) {
        this.bukkitConfiguration = plugin.getClass().getAnnotation(BukkitConfiguration.class);

        beans.put(Plugin.class, plugin);
        beans.put(JavaPlugin.class, plugin);
        beans.put(plugin.getClass(), plugin);
        beans.put(BukkitContext.class, this);

        configure(plugin);

        classes.addAll(loadClasses(getFileFromPlugin(plugin), plugin.getClass()));
        buildDependencyGraph();
        sortedClasses.addAll(topologicalSort());
        createBeans();
        autowiredAll(plugin, plugin.getClass());
        callInit();
    }



    private void configure(JavaPlugin plugin) {
        if(bukkitConfiguration == null) return;

        if(bukkitConfiguration.enableConfig())
            ConfigLoader.configure(this, plugin);
    }


    private void buildDependencyGraph() {
        for (Class clazz : classes) {
            if(clazz.isInterface() || clazz.isEnum() || clazz.isAnnotation())
                continue;

            Component component = getComponentAnnotation(clazz);
            if (component == null)
                continue;

            dependencyGraph.putIfAbsent(clazz, new ArrayList<>());

            for (Class dependency : clazz.getDeclaredConstructors()[0].getParameterTypes())
                dependencyGraph.get(clazz).add(dependency);
        }
    }

    private List> topologicalSort() {
        List> sorted = new ArrayList<>();
        Set> visited = new HashSet<>();
        Set> visiting = new HashSet<>();

        for (Class clazz : classes)
            if (!visited.contains(clazz))
                visit(clazz, visited, visiting, sorted);

        return sorted;
    }



    private void createBeans() {
        for (Class clazz : sortedClasses) {
            if (beans.containsKey(clazz))
                continue;

            Constructor[] constructors = clazz.getDeclaredConstructors();
            if(constructors.length == 0)
                throw new RuntimeException("Not found constructor for class: " + clazz.getName());

            Constructor constructor = constructors[0];
            constructor.setAccessible(true);

            Class[] parameterTypes = constructor.getParameterTypes();
            Object[] parameters = new Object[parameterTypes.length];

            for (int i = 0; i < parameterTypes.length; i++) {
                Class paramType = parameterTypes[i];
                Object dependency = beans.get(paramType);

                if (dependency == null)
                    throw new RuntimeException("Dependency not found for class: " + paramType.getName());

                parameters[i] = dependency;
            }


            beans.put(clazz, newInstanceOfObject(clazz, constructor, parameters));
        }
    }

    private void autowiredAll(JavaPlugin plugin, Class clazz) {
        for(Class c : getAllClasses(getFileFromPlugin(plugin), clazz)) {
            for(Field field : c.getDeclaredFields()) {
                if(!Modifier.isStatic(field.getModifiers())) continue;

                Autowired autowired = field.getAnnotation(Autowired.class);
                if(autowired == null) continue;

                Object bean = getBean(field.getType());
                if(bean == null)
                    throw new RuntimeException("Bean of class: " + field.getType().getSimpleName() + " for autowired to: " + c.getSimpleName() + " not founded!");

                field.setAccessible(true);
                try { field.set(null, bean); }
                catch (IllegalAccessException e) { throw new RuntimeException(e); }
            }
        }
    }

    private void callInit() {
        for(Class clazz : sortedClasses) {
            for(Method method : clazz.getDeclaredMethods()) {
                Init init = method.getAnnotation(Init.class);
                if(init == null) continue;

                method.setAccessible(true);
                try { method.invoke(getBean(clazz)); }
                catch (Exception e) { throw new RuntimeException(e); }
            }
        }
    }

    private File getFileFromPlugin(JavaPlugin javaPlugin) {
        try {
            Field field = JavaPlugin.class.getDeclaredField("file");
            field.setAccessible(true);

            return (File) field.get(javaPlugin);
        }
        catch (Exception e) { throw new RuntimeException(e);}
    }


    public  T getBean(Class clazz) {
        return clazz.cast(beans.get(clazz));
    }


    public void addBean(Object object) {
        beans.put(object.getClass(), object);
    }

    public void addPreloadedClass(Class clazz) {
        classes.add(clazz);
    }



    private Object newInstanceOfObject(Class clazz, Constructor constructor, Object[] parameters) {
        if(bukkitConfiguration != null) {
            if(bukkitConfiguration.enableConfig()) {
                Config config = clazz.getAnnotation(Config.class);
                if (config != null)
                    return ConfigLoader.instanceOfConfig(this, config, clazz);
            }

        }


        Object instance;
        try { instance = constructor.newInstance(parameters); }
        catch (Exception e) { throw new RuntimeException(e); }

        return instance;
    }



    private static List> loadClasses(File jarFile, Class clazz) {
        return ClassUtils.scan(jarFile, clazz).stream()
                .filter((c) -> {
                    if(c.isInterface() || c.isEnum() || c.isAnnotation())
                        return false;

                    return getComponentAnnotation(c) != null;
                })
                .toList();
    }


    private static List> getAllClasses(File jarFile, Class clazz) {
        return ClassUtils.scan(jarFile, clazz).stream()
                .filter((c) -> !c.isInterface() && !c.isEnum() && !c.isAnnotation())
                .toList();
    }


    private static Component getComponentAnnotation(Class clazz) {
        Component component = clazz.getAnnotation(Component.class);
        if(component != null)
            return component;

        for(Annotation annotation : clazz.getAnnotations()) {
            component = annotation.annotationType().getAnnotation(Component.class);
            if(component != null)
                return component;
        }

        return null;
    }


    private void visit(Class clazz, Set> visited, Set> visiting, List> sorted) {
        if (visiting.contains(clazz))
            throw new RuntimeException("Cyclic dependency detected for class: " + clazz.getName());

        if (visited.contains(clazz))
            return;

        visiting.add(clazz);
        for (Class dependency : dependencyGraph.getOrDefault(clazz, Collections.emptyList()))
            visit(dependency, visited, visiting, sorted);

        visiting.remove(clazz);
        visited.add(clazz);
        sorted.add(clazz);
    }





}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy