SimplexCore/src/main/java/io/github/simplexdev/simplexcore/command/CommandLoader.java

201 lines
8.8 KiB
Java

package io.github.simplexdev.simplexcore.command;
import io.github.simplexdev.api.annotations.CommandInfo;
import io.github.simplexdev.simplexcore.SimplexCorePlugin;
import io.github.simplexdev.simplexcore.module.SimplexModule;
import io.github.simplexdev.simplexcore.utils.ReflectionTools;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.SimplePluginManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.reflections.Reflections;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.MissingResourceException;
public final class CommandLoader {
private Reflections reflections;
private ClassLoader classLoader;
private SimplexModule<?> plugin;
private static final CommandLoader instance = new CommandLoader();
private final Registry registry = new Registry();
/**
* @return A Singleton Pattern instance of this class.
*/
public static CommandLoader getInstance() {
return instance;
}
/**
* Prepares the CommandLoader to load your plugin's commands from its own package location.
* This is synchronized, so it only registers commands from one plugin at a time.
* All your commands MUST be placed in their own package.
* <p>
* If your command classes do not have the {@link CommandInfo} annotation, they will not be loaded.
* If the class provided does not have the {@link CommandInfo} annotation, the loader will throw a new
* {@link MissingResourceException} and will not load your plugin's commands.
* If the class provided does not extend {@link SimplexCommand}, the loader will throw a new
* {@link CommandLoaderException} and your commands will not be loaded.
* </p>
*
* @param clazz The command class to load from
* @return An instance of this where the classpath has been prepared for loading the commands.
*/
public <T extends SimplexCommand> CommandLoader classpath(SimplexModule<?> plugin, Class<T> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("The class provided cannot be found!");
}
if (!clazz.isAnnotationPresent(CommandInfo.class)) {
throw new MissingResourceException("Cannot register this class as the main resource location!", clazz.getSimpleName(), "@CommandInfo");
}
if (!SimplexCommand.class.isAssignableFrom(clazz)) {
throw new CommandLoaderException("Your command must extend SimplexCommand.class for it to be used as the reference point for loading commands.");
}
this.reflections = ReflectionTools.reflect(clazz);
this.plugin = plugin;
this.classLoader = plugin.getClass().getClassLoader();
return this;
}
/**
* Loads all the commands from the specified classpath.
* This should be used immediately after {@link CommandLoader#classpath(SimplexModule, Class)} has been called.
* If used before, an exception will be thrown, and your commands will not be loaded.
*/
public void load() {
if (reflections == null || plugin == null || classLoader == null) {
throw new CommandLoaderException("Please run CommandLoader#classpath(SimplexModule, Class) first!");
}
reflections.getTypesAnnotatedWith(CommandInfo.class).forEach(annotated -> {
CommandInfo info = annotated.getDeclaredAnnotation(CommandInfo.class);
if (info == null) {
SimplexCorePlugin.getInstance()
.getLogger().warning(annotated.getSimpleName()
+ " is missing a required annotation: "
+ CommandInfo.class.getSimpleName());
return;
}
if (!SimplexCommand.class.isAssignableFrom(annotated)) {
SimplexCorePlugin.getInstance()
.getLogger().warning(annotated.getSimpleName()
+ " must extend " + SimplexCommand.class.getSimpleName()
+ " to be registered as a command.");
return;
}
PluginCommand command = registry.create(plugin, info.name().toLowerCase());
command.setAliases(Arrays.asList(info.aliases().split(",")));
command.setDescription(info.description());
command.setExecutor(getExecutorFromName(info.name()));
command.setLabel(info.name().toLowerCase());
command.setPermission(info.permission());
command.setPermissionMessage(info.permissionMessage());
command.setTabCompleter(getTabFromName(info.name()));
command.setUsage(info.usage());
registry.registerCommand(command);
});
}
/**
* Gets the command class as a child of {@link CommandExecutor} from the {@link CommandInfo#name()} annotation.
* This is for registering the CommandExecutor of the provided command with Bukkit.
* This should only be used by the CommandLoader.
*
* @param name The name of the command.
* @return An instance of the command class as a CommandExecutor.
*/
private CommandExecutor getExecutorFromName(String name) {
for (Class<? extends SimplexCommand> obj : reflections.getSubTypesOf(SimplexCommand.class)) {
if (!obj.isAnnotationPresent(CommandInfo.class)) {
plugin.getLogger().warning(obj.getSimpleName()
+ " is missing a required annotation: "
+ CommandInfo.class.getSimpleName());
continue;
}
CommandInfo info = obj.getDeclaredAnnotation(CommandInfo.class);
if (name.equalsIgnoreCase(info.name())) {
Constructor<? extends CommandExecutor> constr =
ReflectionTools.getDeclaredConstructor(obj, SimplexModule.class);
if (constr == null) {
throw new CommandLoaderException("Constructor does not exist! Are you extending SimplexCommand properly?");
}
return ReflectionTools.initConstructor(constr, plugin);
}
}
throw new CommandLoaderException("Unable to assign a CommandExecutor from the provided classes!");
}
/**
* Gets the command class as a child of {@link TabCompleter} from the {@link CommandInfo#name()} annotation.
* This is for registering the TabCompleter of the provided command with Bukkit.
* This should only be used by the CommandLoader.
*
* @param name The name of the command
* @return The command as an instance of TabCompleter
*/
@Nullable
private TabCompleter getTabFromName(String name) {
for (Class<? extends SimplexCommand> obj : reflections.getSubTypesOf(SimplexCommand.class)) {
if (!obj.isAnnotationPresent(CommandInfo.class)) {
plugin.getLogger().warning(obj.getSimpleName()
+ " is missing required annotation: "
+ CommandInfo.class.getSimpleName());
continue;
}
CommandInfo info = obj.getDeclaredAnnotation(CommandInfo.class);
if (name.equalsIgnoreCase(info.name())) {
Constructor<? extends TabCompleter> constr = ReflectionTools.getDeclaredConstructor(obj, SimplexModule.class);
if (constr == null) {
throw new CommandLoaderException("Constructor does not exist! Are you extending SimplexCommand properly?");
}
return ReflectionTools.initConstructor(constr, plugin);
}
}
throw new CommandLoaderException("Unable to assign a TabCompleter from the provided classes!");
}
/**
* Registry class, which forces all necessary fields to accessible.
*/
private final class Registry {
private final Constructor<PluginCommand> constructor;
private final Field cmdMapField;
public Registry() {
constructor = ReflectionTools.getDeclaredConstructor(PluginCommand.class, String.class, Plugin.class);
cmdMapField = ReflectionTools.getDeclaredField(SimplePluginManager.class, "commandMap");
}
public PluginCommand create(@NotNull SimplexModule<?> plugin, @NotNull String name) {
return ReflectionTools.initConstructor(constructor, name, plugin);
}
public void registerCommand(PluginCommand command) {
try {
CommandMap map = (CommandMap) cmdMapField.get(plugin.getManager());
map.register(command.getName().toLowerCase(), command);
} catch (IllegalAccessException e) {
throw new CommandLoaderException(e);
}
}
}
}