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

net.kyori.adventure.text.serializer.craftbukkit.MinecraftReflection Maven / Gradle / Ivy

/*
 * This file is part of adventure-platform, licensed under the MIT License.
 *
 * Copyright (c) 2018-2020 KyoriPowered
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package net.kyori.adventure.text.serializer.craftbukkit;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.util.Arrays;

import com.google.common.annotations.Beta;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static java.util.Objects.requireNonNull;

/**
 * Reflection utilities for accessing {@code net.minecraft.server}.
 *
 * 

This is not an official API and can break at any time. You've been warned.

*/ @Beta // Causes users to see "UnstableApiUsage" @ApiStatus.Internal @SuppressWarnings("FilteringWriteTag") // NON-API, no compatibility information needs tracking public final class MinecraftReflection { private MinecraftReflection() { } private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final String PREFIX_NMS = "net.minecraft.server"; private static final String PREFIX_MC = "net.minecraft."; private static final String PREFIX_CRAFTBUKKIT = "org.bukkit.craftbukkit"; private static final String CRAFT_SERVER = "CraftServer"; private static final @Nullable String VERSION; static { final Class serverClass = Bukkit.getServer().getClass(); // TODO: use reflection here too? if(!serverClass.getSimpleName().equals(CRAFT_SERVER)) { VERSION = null; } else if(serverClass.getName().equals(PREFIX_CRAFTBUKKIT + "." + CRAFT_SERVER)) { VERSION = "."; } else { String name = serverClass.getName(); name = name.substring(PREFIX_CRAFTBUKKIT.length()); name = name.substring(0, name.length() - CRAFT_SERVER.length()); VERSION = name; } } /** * Gets a class by the first name available. * * @param classNames an array of class names to try in order * @return a class or {@code null} if not found */ public static @Nullable Class findClass(final @Nullable String@NotNull... classNames) { for(final String clazz : classNames) { if(clazz == null) continue; try { final Class classObj = Class.forName(clazz); return classObj; } catch(final ClassNotFoundException e) { } } return null; } /** * Gets a {@code net.minecraft} class. * * @param className a class name, without the {@code net.minecraft} prefix * @return a class * @throws NullPointerException if the class was not found */ public static @NotNull Class needClass(final @Nullable String@NotNull... className) { return requireNonNull(findClass(className), "Could not find class from candidates" + Arrays.toString(className)); } /** * Gets whether a class is available. * * @param classNames an array of class names to try in order * @return if the class is loaded */ public static boolean hasClass(final @NotNull String... classNames) { return findClass(classNames) != null; } /** * Gets a handle for a class method. * * @param holderClass a class * @param methodName a method name * @param returnClass a method return class * @param parameterClasses an array of method parameter classes * @return a method handle or {@code null} if not found */ public static @Nullable MethodHandle findMethod(final @Nullable Class holderClass, final String methodName, final @Nullable Class returnClass, final Class... parameterClasses) { return findMethod(holderClass, new String[] {methodName}, returnClass, parameterClasses); } /** * Gets a handle for a class method. * * @param holderClass a class * @param methodNames a method name * @param returnClass a method return class * @param parameterClasses an array of method parameter classes * @return a method handle or {@code null} if not found */ public static @Nullable MethodHandle findMethod(final @Nullable Class holderClass, final @Nullable String@NotNull[] methodNames, final @Nullable Class returnClass, final Class... parameterClasses) { if(holderClass == null || returnClass == null) return null; for(final Class parameterClass : parameterClasses) { if(parameterClass == null) return null; } for(final String methodName : methodNames) { if(methodName == null) continue; try { return LOOKUP.findVirtual(holderClass, methodName, MethodType.methodType(returnClass, parameterClasses)); } catch(final NoSuchMethodException | IllegalAccessException e) { } } return null; } /** * Gets a handle for a class method. * * @param holderClass a class * @param methodNames a method name * @param returnClass a method return class * @param parameterClasses an array of method parameter classes * @return a method handle or {@code null} if not found */ public static @Nullable MethodHandle findStaticMethod(final @Nullable Class holderClass, final String methodNames, final @Nullable Class returnClass, final Class... parameterClasses) { return findStaticMethod(holderClass, new String[]{methodNames}, returnClass, parameterClasses); } /** * Gets a handle for a class method. * * @param holderClass a class * @param methodNames a method name * @param returnClass a method return class * @param parameterClasses an array of method parameter classes * @return a method handle or {@code null} if not found */ public static @Nullable MethodHandle findStaticMethod(final @Nullable Class holderClass, final String[] methodNames, final @Nullable Class returnClass, final Class... parameterClasses) { if(holderClass == null || returnClass == null) return null; for(final Class parameterClass : parameterClasses) { if(parameterClass == null) return null; } for(final String methodName : methodNames) { try { return LOOKUP.findStatic(holderClass, methodName, MethodType.methodType(returnClass, parameterClasses)); } catch(final NoSuchMethodException | IllegalAccessException e) { } } return null; } /** * Gets whether a class has a method. * * @param holderClass a class * @param names field name candidates, will be checked in order * @param type the field type * @return if the method exists */ public static boolean hasField(final @Nullable Class holderClass, final Class type, final String... names) { if(holderClass == null) return false; for(final String name : names) { try { final Field field = holderClass.getDeclaredField(name); if(field.getType() == type) return true; } catch(final NoSuchFieldException e) { // continue } } return false; } /** * Gets whether a class has a method. * * @param holderClass a class * @param methodName a method name * @param parameterClasses an array of method parameter classes * @return if the method exists */ public static boolean hasMethod(final @Nullable Class holderClass, final String methodName, final Class... parameterClasses) { return hasMethod(holderClass, new String[] {methodName}, parameterClasses); } /** * Gets whether a class has a method. * * @param holderClass a class * @param methodNames a method name * @param parameterClasses an array of method parameter classes * @return if the method exists */ public static boolean hasMethod(final @Nullable Class holderClass, final String[] methodNames, final Class... parameterClasses) { if(holderClass == null) return false; for(final Class parameterClass : parameterClasses) { if(parameterClass == null) return false; } for(final String methodName : methodNames) { try { holderClass.getMethod(methodName, parameterClasses); return true; } catch(final NoSuchMethodException e) { } } return false; } /** * Gets a handle for a class constructor. * * @param holderClass a class * @param parameterClasses an array of method parameter classes * @return a method handle or {@code null} if not found */ public static @Nullable MethodHandle findConstructor(final @Nullable Class holderClass, final @Nullable Class... parameterClasses) { if(holderClass == null) return null; for(final Class parameterClass : parameterClasses) { if(parameterClass == null) return null; } try { return LOOKUP.findConstructor(holderClass, MethodType.methodType(void.class, parameterClasses)); } catch(final NoSuchMethodException | IllegalAccessException e) { return null; } } /** * Gets a class field and makes it accessible. * * @param holderClass a class * @param fieldName a field name * @return an accessible field * @throws NoSuchFieldException when thrown by {@link Class#getDeclaredField(String)} */ public static @NotNull Field needField(final @NotNull Class holderClass, final @NotNull String fieldName) throws NoSuchFieldException { final Field field = holderClass.getDeclaredField(fieldName); field.setAccessible(true); return field; } /** * Gets a class field if possible and makes it accessible. * * @param holderClass a class * @param fieldName a field name * @return an accessible field */ public static @Nullable Field findField(final @Nullable Class holderClass, final @NotNull String... fieldName) { return findField(holderClass, null, fieldName); } /** * Gets a class field if it exists and is of the appropriate type and makes it accessible. * * @param holderClass a class * @param expectedType the expected type of the field * @param fieldNames a field name * @return an accessible field */ public static @Nullable Field findField(final @Nullable Class holderClass, final @Nullable Class expectedType, final @NotNull String... fieldNames) { if(holderClass == null) return null; Field field; for(final String fieldName : fieldNames) { try { field = holderClass.getDeclaredField(fieldName); } catch(final NoSuchFieldException ex) { continue; } field.setAccessible(true); if(expectedType != null && !expectedType.isAssignableFrom(field.getType())) { continue; } return field; } return null; } /** * Return a method handle that can set the value of the provided field. * * @param field the field to set * @return a handle, if accessible */ public static @Nullable MethodHandle findSetterOf(final @Nullable Field field) { if(field == null) return null; try { return LOOKUP.unreflectSetter(field); } catch(final IllegalAccessException e) { return null; } } /** * Return a method handle that can get the value of the provided field. * * @param field the field to get * @return a handle, if accessible */ public static @Nullable MethodHandle findGetterOf(final @Nullable Field field) { if(field == null) return null; try { return LOOKUP.unreflectGetter(field); } catch(final IllegalAccessException e) { return null; } } /** * Gets an enum value. * * @param enumClass an enum class * @param enumName an enum name * @return an enum value or {@code null} if not found */ public static @Nullable Object findEnum(final @Nullable Class enumClass, final @NotNull String enumName) { return findEnum(enumClass, enumName, Integer.MAX_VALUE); } /** * Gets an enum value. * * @param enumClass an enum class * @param enumName an enum name * @param enumFallbackOrdinal an enum ordinal, when the name is not found * @return an enum value or {@code null} if not found */ @SuppressWarnings("unchecked") public static @Nullable Object findEnum(final @Nullable Class enumClass, final @NotNull String enumName, final int enumFallbackOrdinal) { if(enumClass == null || !Enum.class.isAssignableFrom(enumClass)) { return null; } try { return Enum.valueOf(enumClass.asSubclass(Enum.class), enumName); } catch(final IllegalArgumentException e) { final Object[] constants = enumClass.getEnumConstants(); if(constants.length > enumFallbackOrdinal) { return constants[enumFallbackOrdinal]; } } return null; } /** * Gets whether CraftBukkit is available. * * @return if CraftBukkit is available */ public static boolean isCraftBukkit() { return VERSION != null; } /** * Gets a {@code org.bukkit.craftbukkit} class name. * * @param className a class name, without the {@code org.bukkit.craftbukkit} prefix * @return a class name or {@code null} if not found */ public static @Nullable String findCraftClassName(final @NotNull String className) { return isCraftBukkit() ? PREFIX_CRAFTBUKKIT + VERSION + className : null; } /** * Gets a {@code org.bukkit.craftbukkit} class. * * @param className a class name, without the {@code org.bukkit.craftbukkit} prefix * @return a class or {@code null} if not found */ public static @Nullable Class findCraftClass(final @NotNull String className) { final String craftClassName = findCraftClassName(className); if(craftClassName == null) { return null; } return findClass(craftClassName); } /** * Gets a {@code org.bukkit.craftbukkit} class. * * @param className a class name, without the {@code org.bukkit.craftbukkit} prefix * @param superClass a super class * @param a super type * @return a class or {@code null} if not found */ public static @Nullable Class findCraftClass(final @NotNull String className, final @NotNull Class superClass) { final Class craftClass = findCraftClass(className); if(craftClass == null || !requireNonNull(superClass, "superClass").isAssignableFrom(craftClass)) { return null; } return craftClass.asSubclass(superClass); } /** * Gets a {@code org.bukkit.craftbukkit} class. * * @param className a class name, without the {@code org.bukkit.craftbukkit} prefix * @return a class * @throws NullPointerException if the class was not found */ public static @NotNull Class needCraftClass(final @NotNull String className) { return requireNonNull(findCraftClass(className), "Could not find org.bukkit.craftbukkit class " + className); } /** * Gets a {@code net.minecraft.server} class name. * * @param className a class name, without the {@code net.minecraft.server} prefix * @return a class name or {@code null} if not found */ public static @Nullable String findNmsClassName(final @NotNull String className) { return isCraftBukkit() ? PREFIX_NMS + VERSION + className : null; } /** * Get a {@code net.minecraft.server} class. * * @param className a class name, without the {@code net.minecraft.server} prefix * @return a class name or {@code null} if not found */ public static @Nullable Class findNmsClass(final @NotNull String className) { final String nmsClassName = findNmsClassName(className); if(nmsClassName == null) { return null; } return findClass(nmsClassName); } /** * Gets a {@code net.minecraft.server} class. * * @param className a class name, without the {@code org.bukkit.craftbukkit} prefix * @return a class * @throws NullPointerException if the class was not found */ public static @NotNull Class needNmsClass(final @NotNull String className) { return requireNonNull(findNmsClass(className), "Could not find net.minecraft.server class " + className); } /** * Gets a {@code net.minecraft} class name. * * @param className a class name, without the {@code net.minecraft} prefix * @return a class name or {@code null} if not found */ public static @Nullable String findMcClassName(final @NotNull String className) { return isCraftBukkit() ? PREFIX_MC + className : null; } /** * Get a {@code net.minecraft} class. * * @param classNames a class name, without the {@code net.minecraft} prefix * @return a class name or {@code null} if not found */ public static @Nullable Class findMcClass(final @NotNull String... classNames) { for(final String clazz : classNames) { final String nmsClassName = findMcClassName(clazz); if(nmsClassName != null) { final Class candidate = findClass(nmsClassName); if(candidate != null) { return candidate; } } } return null; } /** * Gets a {@code net.minecraft} class. * * @param className a class name, without the {@code net.minecraft} prefix * @return a class * @throws NullPointerException if the class was not found */ public static @NotNull Class needMcClass(final @NotNull String... className) { return requireNonNull(findMcClass(className), "Could not find net.minecraft class from candidates" + Arrays.toString(className)); } /** * Gets the singleton method handle lookup. * * @return the method handle lookup */ public static MethodHandles.@NotNull Lookup lookup() { return LOOKUP; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy