ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ExactReflection.java
Pasqual Koschmieder c5f0550953
Use MethodHandles for reflection (#1561)
* don't enforce async calls for thread-safe listeners (closes #1551)
* cleanups, remove structure compiling
* improve cloning a bit
* fix small issue in no-op structure modifier
* remove last usages of FieldUtils
* improve and fix equality check in container test
2022-07-24 10:16:05 -04:00

214 lines
7.1 KiB
Java

package com.comphenix.protocol.reflect;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class ExactReflection {
private static final Joiner COMMA_SEPERATED_JOINER = Joiner.on(", ");
// The class we're actually representing
private final Class<?> source;
private final boolean forceAccess;
private ExactReflection(Class<?> source, boolean forceAccess) {
this.source = Preconditions.checkNotNull(source, "source class cannot be NULL");
this.forceAccess = forceAccess;
}
/**
* Retrieves an exact reflection instance from a given class.
*
* @param source - the class we'll use.
* @param forceAccess - whether to also search for members which are out of the allowed scope.
* @return A fuzzy reflection instance.
*/
public static ExactReflection fromClass(Class<?> source, boolean forceAccess) {
return new ExactReflection(source, forceAccess);
}
/**
* Retrieves an exact reflection instance from an object.
*
* @param reference - the object we'll use.
* @param forceAccess - whether to also search for members which are out of the allowed scope.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static ExactReflection fromObject(Object reference, boolean forceAccess) {
return new ExactReflection(reference.getClass(), forceAccess);
}
/**
* Retrieve the first method in the class hierarchy with the given name and parameters.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for methods which are out of the caller scope.
*
* @param methodName - the name of the method to find, NULL to only search by using the given parameters.
* @param parameters - the parameters of the method to find.
* @return the first matching method.
* @throws IllegalArgumentException if there is no method with the given name and parameter types.
*/
public Method getMethod(String methodName, Class<?>... parameters) {
Method method = this.lookupMethod(this.source, methodName, parameters);
if (method == null) {
throw new IllegalArgumentException(String.format(
"Unable to find method %s(%s) in %s",
methodName,
COMMA_SEPERATED_JOINER.join(parameters),
this.source.getName()));
}
return method;
}
/**
* Finds the first method in the class hierarchy with the given name and parameters.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for methods which are out of the caller scope.
*
* @param methodName - the name of the method to find, NULL to only search by using the given parameters.
* @param parameters - the parameters of the method to find.
* @return the first matching method, NULL if no method matches.
*/
public Method findMethod(String methodName, Class<?>... parameters) {
return this.lookupMethod(this.source, methodName, parameters);
}
// For recursion
private Method lookupMethod(Class<?> instanceClass, String methodName, Class<?>... parameters) {
for (Method method : instanceClass.getDeclaredMethods()) {
if ((this.forceAccess || Modifier.isPublic(method.getModifiers()))
&& (methodName == null || method.getName().equals(methodName))
&& Arrays.equals(method.getParameterTypes(), parameters)) {
return method;
}
}
// Search in every superclass
if (instanceClass.getSuperclass() != null) {
return this.lookupMethod(instanceClass.getSuperclass(), methodName, parameters);
}
return null;
}
/**
* Retrieve a field in the class hierarchy by the given name.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for fields which are out of the caller scope.
*
* @param fieldName - the name of the field to find.
* @return the first matching field.
* @throws IllegalArgumentException if no field with the given name was found.
*/
public Field getField(String fieldName) {
Field field = this.lookupField(this.source, fieldName);
if (field == null) {
throw new IllegalArgumentException(String.format(
"Unable to find field with name %s in %s.",
fieldName,
this.source.getName()));
}
return field;
}
/**
* Finds a field in the class hierarchy by the given name.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for fields which are out of the caller scope.
*
* @param fieldName - the name of the field to find.
* @return the first matching field, null if no field matches.
*/
public Field findField(String fieldName) {
return this.lookupField(this.source, fieldName);
}
// For recursion
private Field lookupField(Class<?> instanceClass, String fieldName) {
for (Field field : instanceClass.getDeclaredFields()) {
if ((this.forceAccess || Modifier.isPublic(field.getModifiers())) && field.getName().equals(fieldName)) {
return field;
}
}
// Recursively find the correct field
if (instanceClass.getSuperclass() != null) {
return this.lookupField(instanceClass.getSuperclass(), fieldName);
}
return null;
}
/**
* Retrieves the first constructor in the class hierarchy with the given parameters.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for constructors which are out of the caller scope.
*
* @param parameters - the parameters of the constructor to find.
* @return the first matching constructor.
* @throws IllegalArgumentException if no constructor with the given parameters was found.
*/
public Constructor<?> getConstructor(Class<?>... parameters) {
Constructor<?> constructor = this.findConstructor(parameters);
if (constructor == null) {
throw new IllegalArgumentException(String.format(
"Unable to find constructor (%s) in %s",
COMMA_SEPERATED_JOINER.join(parameters),
this.source.getName()));
}
return constructor;
}
/**
* Finds the first constructor in the class hierarchy with the given parameters.
* <p>
* If {@link #isForceAccess()} is TRUE, we will also search for constructors which are out of the caller scope.
*
* @param parameters - the parameters of the constructor to find.
* @return the first matching constructor, NULL if no constructor matches.
*/
public Constructor<?> findConstructor(Class<?>... parameters) {
try {
Constructor<?> constructor = this.source.getDeclaredConstructor(parameters);
return this.forceAccess || Modifier.isPublic(constructor.getModifiers()) ? constructor : null;
} catch (NoSuchMethodException exception) {
return null;
}
}
/**
* Retrieve an {@link ExactReflection} object where scope restrictions are ignored.
*
* @return A copy of the current object.
*/
public ExactReflection forceAccess() {
return new ExactReflection(this.source, true);
}
/**
* Determine if we are overriding scope restrictions and will also find private, protected or package members.
*
* @return TRUE if we are, FALSE otherwise.
*/
public boolean isForceAccess() {
return this.forceAccess;
}
/**
* Retrieve the source class we are searching.
*
* @return The source.
*/
public Class<?> getSource() {
return this.source;
}
}