001package com.mrivanplays.commandworker.bukkit.argtypes;
002
003import com.mojang.brigadier.arguments.ArgumentType;
004import com.mrivanplays.commandworker.bukkit.registry.CmdRegistryHandler;
005import java.lang.reflect.Constructor;
006import java.lang.reflect.Field;
007import java.lang.reflect.InvocationTargetException;
008import java.lang.reflect.Method;
009import java.util.Arrays;
010import org.bukkit.Bukkit;
011import org.bukkit.NamespacedKey;
012
013/**
014 * Represents a utility class for accessing {@link ArgumentType}s provided by mojang in the server's
015 * internals.
016 */
017public class MinecraftArgumentTypesAccessor {
018
019  private MinecraftArgumentTypesAccessor() {}
020
021  private static Constructor<?> MINECRAFT_KEY_CONSTRUCTOR;
022  private static Method ARGUMENT_REGISTRY_GET_BY_KEY_METHOD;
023  private static Field ARGUMENT_REGISTRY_ENTRY_CLASS_FIELD;
024
025  static {
026    if (CmdRegistryHandler.isSupported()) {
027      String nmsVersion =
028          Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3];
029      try {
030        Class<?> minecraftKey =
031            Class.forName("net.minecraft.server." + nmsVersion + ".MinecraftKey");
032        MINECRAFT_KEY_CONSTRUCTOR = minecraftKey.getConstructor(String.class, String.class);
033        MINECRAFT_KEY_CONSTRUCTOR.setAccessible(true);
034
035        Class<?> argumentRegistry =
036            Class.forName("net.minecraft.server." + nmsVersion + ".ArgumentRegistry");
037        ARGUMENT_REGISTRY_GET_BY_KEY_METHOD =
038            Arrays.stream(argumentRegistry.getDeclaredMethods())
039                .filter(method -> method.getParameterCount() == 1)
040                .filter(method -> minecraftKey.equals(method.getParameterTypes()[0]))
041                .findFirst()
042                .orElseThrow(NoSuchMethodException::new);
043        ARGUMENT_REGISTRY_GET_BY_KEY_METHOD.setAccessible(true);
044
045        Class<?> argumentRegistryEntry = ARGUMENT_REGISTRY_GET_BY_KEY_METHOD.getReturnType();
046        ARGUMENT_REGISTRY_ENTRY_CLASS_FIELD =
047            Arrays.stream(argumentRegistryEntry.getDeclaredFields())
048                .filter(field -> field.getType() == Class.class)
049                .findFirst()
050                .orElseThrow(NoSuchFieldException::new);
051        ARGUMENT_REGISTRY_ENTRY_CLASS_FIELD.setAccessible(true);
052      } catch (NoSuchMethodException | NoSuchFieldException | ClassNotFoundException e) {
053        throw new ExceptionInInitializerError(e);
054      }
055    }
056  }
057
058  public static void ensureSetup() {
059    // do nothing; this is enough to trigger the static initializer
060  }
061
062  /**
063   * Retrieves the registered argument type's class, matching the key specified, if brigadier is
064   * supported.
065   *
066   * @param key the key of the argument type you want to retrieve
067   * @return argument type class
068   */
069  @SuppressWarnings("unchecked")
070  public static Class<? extends ArgumentType<?>> getArgumentClass(NamespacedKey key) {
071    if (CmdRegistryHandler.isSupported()) {
072      try {
073        Object minecraftKey =
074            MINECRAFT_KEY_CONSTRUCTOR.newInstance(key.getNamespace(), key.getKey());
075        Object entry = ARGUMENT_REGISTRY_GET_BY_KEY_METHOD.invoke(null, minecraftKey);
076        if (entry == null) {
077          throw new IllegalArgumentException(
078              "No such ArgumentType with key '" + key.toString() + "'");
079        }
080
081        Class<?> argument = (Class<?>) ARGUMENT_REGISTRY_ENTRY_CLASS_FIELD.get(entry);
082        return (Class<? extends ArgumentType<?>>) argument;
083      } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
084        return null;
085      }
086    }
087    return null;
088  }
089
090  /**
091   * Retrieves the registered argument type, matching the key specified, if brigadier is supported.
092   *
093   * @param key the key of the argument type you want to retrieve
094   * @return argument type
095   */
096  public static ArgumentType<?> getByKey(NamespacedKey key) {
097    if (CmdRegistryHandler.isSupported()) {
098      try {
099        Constructor<? extends ArgumentType<?>> argumentConstructor =
100            getArgumentClass(key).getDeclaredConstructor();
101        argumentConstructor.setAccessible(true);
102        return argumentConstructor.newInstance();
103      } catch (NullPointerException
104          | InstantiationException
105          | InvocationTargetException
106          | NoSuchMethodException
107          | IllegalAccessException e) {
108        return null;
109      }
110    }
111    return null;
112  }
113}