package org.stianloader.mrjmania; import java.lang.invoke.MethodHandle; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.Modifier; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Polyfills for reflection-related workloads. */ public class MRJReflection { private MRJReflection() { throw new UnsupportedOperationException(); } /** * Invoke AccessibleObject#canAccess if available (J9+), or use the more imprecise * {@link AccessibleObject#isAccessible()} that can be error-prone in a post-JPMS world. * * @param subject The field/method/constructor that is the subject of this operation. * @param accessedInstance The instance for non-static fields and methods. Otherwise {@code null}. * @return true if the {@code subject} can be accessed if using {@code accessedInstance} as the implementing instance of the member. * @throws IllegalStateException If the nullability constraints for {@code accessedInstance} were violated. */ @Contract(pure = true) public static boolean canAccess(@NotNull AccessibleObject subject, @Nullable Object accessedInstance) { if ((subject instanceof Constructor || (subject instanceof Member && Modifier.isStatic(((Member) subject).getModifiers())))) { if (accessedInstance != null) { throw new IllegalStateException("accessedInstance must be null for constructors or static members."); } } else { if (accessedInstance == null) { throw new IllegalStateException("accessedInstance may not be null for non-static members."); } } // Technically, this is wrong - it just shows whether reflection access limitations are lifted. // However doing all the access checking stuff is quite a bit extreme - so we will leave it at this // - especially given that #trySetAccessible will usually be called afterwards. return subject.isAccessible(); } /** * Returns a {@link MethodHandle}, that has the same method signature and behaviour as the input {@link MethodHandle}, * but returns {@code void} and discards the result value. * *
This method uses a polyfill for Java versions ≤ 15 that seems to work but might not be guaranteed * to do so as even I don't quite understand it. If this method doesn't work as advertised, then that might be the cause. * * @param handle The {@link MethodHandle} that has a return value to drop. * @return The {@link MethodHandle} without a return value. */ @NotNull @Contract(pure = true) public static MethodHandle dropReturn(@NotNull MethodHandle handle) { return handle.asType(handle.type().changeReturnType(void.class)); } /** * Attempt to make the given {@link AccessibleObject} (constructor/method/field) accessible. * *
This method will swallow any somewhat expected exceptions, which might not always be desirable. * Just use your debugger though. * * @param subject The {@link AccessibleObject} that is the subject of this operation. * @return {@code true} if succeeded, false otherwise. */ public static final boolean trySetAccessible(@NotNull AccessibleObject subject) { try { subject.setAccessible(true); return true; } catch (Exception e) { return false; } } }