Class XReflection

java.lang.Object
com.cryptomorin.xseries.reflection.XReflection

public final class XReflection extends Object
General Java reflection handler, but specialized for Minecraft NMS/CraftBukkit reflection as well.

Starting Points

Basic reflection starting points are through the ClassHandle methods:
  • of(Class): For static classes with known type at compile time.
  • classHandle(): For general classes that have unknown type at compile time.
  • ofMinecraft(): Specialized for Minecraft-related classes.
  • namespaced(): String-based API for getting classes with Java code inside strings that is more readable.

Fallback

Some methods exist to choose between different values depending on the situation:

Others

Also, there are a few other non-reflection APIs in this class that are a bit "hacky" which is why they're here.

XReflection API Stages

In general, XReflection's API is divided into five stages:
  • Stage I (Raw Level): This is the a low-level API which offers the most customizable and performance. However, although it's much better in terms of readability compared to Java's raw reflection API, it's the least unreadable format of XReflection API. This API is accessed from of(Class), classHandle() and ofMinecraft().
  • Stage II (String API): This API is a lot more readable and offers the same performance compared to Stage I API, however it has an extra overhead for caching ReflectiveHandle for the first time. This API is typically accessed from ReflectiveNamespace's string methods or the signature methods of its related APIs (e.g. MethodMemberHandle.signature(String).
  • Stage III (Proxification): This API is much more readable for invocation compared to the string API because it uses actual interface classes, however, whether its declaration API is more readable than the string API is debatable and depends on the use case. The biggest disadvantage of this API is its caching and invocation performance due to how its internal system is built. The invocation performance is mostly a deal-breaker in most cases. It's accessed from proxify(Class).
  • Stage IV (ASMification): This is simply Stage III API, however instead of relying on Java's proxy system, an ASM-assisted code generation occurs in a new class loader, which makes this almost as fast as direct calls, even for inaccessible private methods due to the use of MethodHandle.invokeExact(Object...) with polymorphic signature. This system is only used if at least ASM9 is detected during runtime (supports shading as well)
  • Stage V (Compile-Time Remapper): ???

Performance & Caching

While the standard XReflection wrapper around Java's reflection API in general is pretty lightweight and doesn't add that much of an overhead, you're expected to cache the results of ReflectiveHandle.reflect() for optimum performance just like using normal reflection. That's because XReflection offers no form of cache, even for the same handle. So you should not cache ReflectiveHandle, but you should cache the results (e.g. ReflectiveHandle.reflect(), ReflectiveHandle.unreflect(), ...) instead.
This is specially important to do if you're going to be using ReflectionParser methods or ReflectiveConstraint since the former uses heavy RegEx patterns which sacrifices a lot of performance for readability, which is really worth it; and the latter adds a bit of extra overhead since it performs checks that the normal reflection API ignores.

As for ReflectiveProxy, you should make sure to always cache your result of proxify(Class). That way you can invoke static methods and constructors from that one proxy and the subsequently created proxies (for individual instances) will inherit that. Although these proxies are handled by an internal cache as well, so it's not that important if you don't cache them.

The results of XReflection creation and execution phases are compared in ReflectionBenchmarkSetup and ReflectionBenchmarkExecution respectively, and the results of the creation phase is only a nanosecond longer for Stage I API and expectedly, a few nanoseconds longer for Stage II API, However there is no difference for the execution phase (maybe 1 or 2 nanoseconds which is the result of data error) but this can't be said for Stage III Proxy API as one might expect, it's almost 4-20 times slower depending on how you're using the code.

Version:
14.0.0
Author:
Crypto Morin
See Also:
  • Field Details

    • NMS_VERSION

      @Nullable @Internal public static final @Nullable String NMS_VERSION
      We use reflection mainly to avoid writing a new class for version barrier. The version barrier is for NMS that uses the Minecraft version as the main package name.

      E.g. EntityPlayer in 1.15 is in the class net.minecraft.server.v1_15_R1 but in 1.14 it's in net.minecraft.server.v1_14_R1 In order to maintain cross-version compatibility we cannot import these classes.

      Performance is not a concern for these specific statically initialized values.

      Versions Legacy

      This will no longer work because of Paper no-relocation strategy.

    • XSERIES_VERSION

      @Internal public static final String XSERIES_VERSION
      The current version of XSeries. Mostly used for the XSkull API.
      See Also:
    • DISABLE_MINECRAFT_CAPABILITIES_PROPERTY

      @TestOnly public static final String DISABLE_MINECRAFT_CAPABILITIES_PROPERTY
      System property (System.getProperty(String)) used to disable Minecraft capabilities of this class. This is useful when working with Unit Tests that don't run a server or needing to run forked processes (e.g. using JMH default forked mode for benchmarking.)

      It's simply enough for the property to be present, but you can also specify a Minecraft version for the class to use for supports(int) and other similar version checking methods.

      See Also:
    • SUPPORTS_ASM

      @Internal public static final boolean SUPPORTS_ASM
      Whether the internal systems can take advantage of ASM-powered systems for maximizing performance for reflection access when possible.
    • MAJOR_NUMBER

      public static final int MAJOR_NUMBER
    • MINOR_NUMBER

      public static final int MINOR_NUMBER
      The raw minor version number. E.g. v1_17_R1 to 17
      Since:
      4.0.0
      See Also:
    • PATCH_NUMBER

      public static final int PATCH_NUMBER
      The raw patch version number. Refers to the major.minor.patch version scheme. E.g.
      • v1.20.4 to 4
      • v1.18.2 to 2
      • v1.19.1 to 1

      I'd not recommend developers to support individual patches at all. You should always support the latest patch. For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4

      This can be used to warn server owners when your plugin will break on older patches.

      Since:
      7.0.0
      See Also:
    • CRAFTBUKKIT_PACKAGE

      @Internal public static final String CRAFTBUKKIT_PACKAGE
      Mojang remapped their NMS in 1.17: Spigot Thread
    • NMS_PACKAGE

      @Internal public static final String NMS_PACKAGE
      Mojang remapped their NMS in 1.17: Spigot Thread
    • SUPPORTED_MAPPINGS

      @Internal @Experimental public static final @Unmodifiable Set<MinecraftMapping> SUPPORTED_MAPPINGS
  • Method Details

    • findNMSVersionString

      @Nullable @Internal public static @Nullable String findNMSVersionString()
    • getVersionInformation

      @NotNull @Contract(pure=true) public static @NotNull String getVersionInformation()
      Gets the full version information of the server. Useful for including in errors. "NMS" might return "Unknown NMS", which means that they're running a Paper server that removed the CraftBukkit NMS version guard.
      Since:
      7.0.0
    • getLatestPatchNumberOf

      @Nullable @Contract(pure=true) public static @Nullable Integer getLatestPatchNumberOf(int minorVersion)
      Gets the latest known patch number of the given minor version. For example: 1.14 -> 4, 1.17 -> 10 The latest version is expected to get newer patches, so make sure to account for unexpected results.
      Parameters:
      minorVersion - the minor version to get the patch number of.
      Returns:
      the patch number of the given minor version if recognized, otherwise null.
      Since:
      7.0.0
    • v

      @NotNull @Contract(value="_, _ -> new", pure=true) public static <T> @NotNull VersionHandle<T> v(int version, T handle)
      Gives the handle object if the server version is equal or greater than the given version. This method is purely for readability and should be always used with VersionHandle.orElse(Object).
      Since:
      5.0.0
      See Also:
    • v

      @NotNull @Contract(value="_, _, _ -> new", pure=true) public static <T> @NotNull VersionHandle<T> v(int version, int patch, T handle)
      Since:
      9.5.0
    • v

      @NotNull @Contract(value="_, _ -> new", pure=true) public static <T> @NotNull VersionHandle<T> v(int version, Callable<T> handle)
    • v

      @NotNull @Contract(value="_, _, _ -> new", pure=true) public static <T> @NotNull VersionHandle<T> v(int version, int patch, Callable<T> handle)
    • supports

      @Contract(pure=true) public static boolean supports(int minorNumber)
      Checks whether the server version is equal or greater than the given version.
      Parameters:
      minorNumber - the version to compare the server version with.
      Returns:
      true if the version is equal or newer, otherwise false.
      Since:
      4.0.0
      See Also:
    • supports

      @Contract(pure=true) public static boolean supports(int majorNumber, int minorNumber, int patchNumber)
      A more friendly version of supports(int, int) for people with OCD.
    • supports

      @Contract(pure=true) public static boolean supports(int minorNumber, int patchNumber)
      Checks whether the server version is equal or greater than the given version.
      Parameters:
      minorNumber - the minor version to compare the server version with.
      patchNumber - the patch number to compare the server version with.
      Returns:
      true if the version is equal or newer, otherwise false.
      Since:
      7.1.0
      See Also:
    • supportsPatch

      @Deprecated @Contract(pure=true) public static boolean supportsPatch(int patchNumber)
      Deprecated.
      Checks whether the server version is equal or greater than the given version.
      Parameters:
      patchNumber - the version to compare the server version with.
      Returns:
      true if the version is equal or newer, otherwise false.
      Since:
      7.0.0
      See Also:
    • getNMSClass

      @NotNull @Deprecated public static @NotNull Class<?> getNMSClass(@Nullable @Nullable String packageName, @NotNull @NotNull String name)
      Deprecated.
      use ofMinecraft() instead.
      Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility.
      Parameters:
      packageName - the 1.17+ package name of this class.
      name - the name of the class.
      Returns:
      the NMS class or null if not found.
      Throws:
      IllegalArgumentException - if the class could not be found.
      Since:
      4.0.0
      See Also:
    • getNMSClass

      @NotNull @Deprecated public static @NotNull Class<?> getNMSClass(@NotNull @NotNull String name)
      Deprecated.
      Get a NMS NMS_PACKAGE class.
      Parameters:
      name - the name of the class.
      Returns:
      the NMS class or null if not found.
      Throws:
      IllegalArgumentException - if the class could not be found.
      Since:
      1.0.0
      See Also:
    • getCraftClass

      @NotNull @Deprecated public static @NotNull Class<?> getCraftClass(@NotNull @NotNull String name)
      Deprecated.
      use ofMinecraft() instead.
      Get a CraftBukkit (org.bukkit.craftbukkit) class.
      Parameters:
      name - the name of the class to load.
      Returns:
      the CraftBukkit class or null if not found.
      Throws:
      IllegalArgumentException - if the class could not be found.
      Since:
      1.0.0
    • toArrayClass

      @NotNull @Contract(pure=true) public static @NotNull Class<?> toArrayClass(@NotNull @NotNull Class<?> clazz)
      Gives an array version of a class. For example if you wanted EntityPlayer[] you'd use:
      
           Class EntityPlayer = ReflectionUtils.getNMSClass("...", "EntityPlayer");
           Class EntityPlayerArray = ReflectionUtils.toArrayClass(EntityPlayer);
       

      Note that this doesn't work on primitive classes.

      Parameters:
      clazz - the class to get the array version of. You could use for multi-dimensions arrays too.
      Throws:
      IllegalArgumentException - if the class could not be found.
      See Also:
    • ofMinecraft

      @NotNull @Contract(value="-> new", pure=true) public static @NotNull MinecraftClassHandle ofMinecraft()
      Since:
      v9.0.0
    • classHandle

      @NotNull @Contract(value="-> new", pure=true) public static @NotNull DynamicClassHandle classHandle()
      Since:
      v9.0.0
    • of

      @NotNull @Contract(value="_ -> new", pure=true) public static @NotNull StaticClassHandle of(Class<?> clazz)
      Since:
      v11.0.0
    • namespaced

      @NotNull @Contract(value="-> new", pure=true) public static @NotNull ReflectiveNamespace namespaced()
      Read ReflectiveNamespace for more info.
      Since:
      v11.0.0
    • any

      @SafeVarargs @NotNull @Contract(value="_ -> new", pure=true) public static <T, H extends ReflectiveHandle<T>> @NotNull AggregateReflectiveHandle<T,H> any(H... handles)
      Since:
      v9.0.0
    • anyOf

      @SafeVarargs @NotNull @Contract(value="_ -> new", pure=true) public static <T, H extends ReflectiveHandle<T>> @NotNull AggregateReflectiveHandle<T,H> anyOf(Callable<H>... handles)
      Since:
      v9.0.0
    • supply

      @Experimental @NotNull @Contract(value="_, _ -> new", pure=true) public static <H extends ReflectiveHandle<?>, O> @NotNull AggregateReflectiveSupplier<H,O> supply(H handle, O object)
      Since:
      v11.3.0
    • supply

      @Experimental @NotNull @Contract(value="_, _ -> new", pure=true) public static <H extends ReflectiveHandle<?>, O> @NotNull AggregateReflectiveSupplier<H,O> supply(H handle, Supplier<O> object)
      Since:
      v11.3.0
    • relativizeSuppressedExceptions

      @Experimental @NotNull @Contract(value="_ -> param1", mutates="param1") public static <T extends Throwable> T relativizeSuppressedExceptions(@NotNull T ex)
      Relativize the stacktrace of exceptions that are thrown from the same location. The suppressed exception's (Throwable.getSuppressed()) stacktrace are relativized against the given exceptions stacktrace.

      This is mostly useful when you have a trial-and-error mechanism that accumulates all the errors to throw them in case all the attempts have failed. This removes unnecessary line information to help the developer focus on important, non-repeated lines.

      Type Parameters:
      T - the type of the exception.
      Parameters:
      ex - the exception to have it's suppressed exceptions relativized.
      Returns:
      the same exception.
    • throwCheckedException

      @Contract(value="_ -> fail", pure=true) public static RuntimeException throwCheckedException(@NotNull @NotNull Throwable exception)
      Throws a checked exception (see Exception) silently without forcing the programmer to handle it. This is usually considered a very bad practice, as those errors are meant to be handled, so please use sparingly. You should just create a RuntimeException instead and putting the checked exception as a cause if necessary.

      Usage

      
           void doStuff() throws IOException {}
      
           void rethrowAsRuntime() {
               try {
                   doStuff();
               } catch (IOException ex) {
                   throw new RuntimeException(ex);
               }
           }
      
           void ignoreTheLawsOfJavaQuantumMechanics() {
               try {
                   doStuff();
               } catch (IOException ex) {
                   throw XReflection.throwCheckedException(ex);
               }
           }
       
      Returns:
      null, but it's intended to be thrown, this is a hacky trick to stop the IDE from complaining about non-terminating statements.
    • stacktrace

      @Experimental @Contract(value="_ -> new", mutates="param1") public static <T> CompletableFuture<T> stacktrace(@NotNull @NotNull CompletableFuture<T> completableFuture)
      Adds the stacktrace of the current thread in case an error occurs in the given Future.
    • concatenate

      @Internal @Contract(value="_, _ -> new", pure=true) public static <T> T[] concatenate(T[] a, T[] b)
    • proxify

      @Experimental @NotNull public static <T extends ReflectiveProxyObject> T proxify(@NotNull @NotNull Class<T> interfaceClass)
      Returns a cached value if this interface is already proxified, otherwise proxifies and returns it.
      See Also: