pandalib-reflect Refactor + javadoc
This commit is contained in:
parent
bc4e8ba261
commit
eea6d2b5b2
@ -47,13 +47,18 @@
|
|||||||
<exclude>META-INF/*.SF</exclude>
|
<exclude>META-INF/*.SF</exclude>
|
||||||
<exclude>META-INF/*.DSA</exclude>
|
<exclude>META-INF/*.DSA</exclude>
|
||||||
<exclude>META-INF/*.RSA</exclude>
|
<exclude>META-INF/*.RSA</exclude>
|
||||||
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</filter>
|
</filter>
|
||||||
</filters>
|
</filters>
|
||||||
<relocations>
|
<relocations>
|
||||||
<relocation>
|
<relocation>
|
||||||
<pattern>io.github.classgraph</pattern>
|
<pattern>io.github.classgraph</pattern>
|
||||||
<shadedPattern>fr.pandacube.shaded.classgraph</shadedPattern>
|
<shadedPattern>fr.pandacube.lib.reflect.shaded.classgraph</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>nonapi.io.github.classgraph</pattern>
|
||||||
|
<shadedPattern>fr.pandacube.lib.reflect.shaded.classgraph.nonapi</shadedPattern>
|
||||||
</relocation>
|
</relocation>
|
||||||
</relocations>
|
</relocations>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/* package */ record ConstructorIdentifier(Class<?>[] parameters) {
|
||||||
|
ConstructorIdentifier {
|
||||||
|
parameters = (parameters == null) ? new Class<?>[0] : parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return other instanceof ConstructorIdentifier o
|
||||||
|
&& Arrays.equals(o.parameters, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Arrays.hashCode(parameters);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/* package */ record MethodIdentifier(String methodName, Class<?>[] parameters) {
|
||||||
|
MethodIdentifier {
|
||||||
|
Objects.requireNonNull(methodName);
|
||||||
|
parameters = (parameters == null) ? new Class<?>[0] : parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return other instanceof MethodIdentifier o
|
||||||
|
&& o.methodName.equals(methodName)
|
||||||
|
&& Arrays.equals(o.parameters, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return methodName.hashCode() ^ Arrays.hashCode(parameters);
|
||||||
|
}
|
||||||
|
}
|
@ -1,465 +1,51 @@
|
|||||||
package fr.pandacube.lib.reflect;
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import io.github.classgraph.ClassGraph;
|
|
||||||
import io.github.classgraph.ClassGraphException;
|
|
||||||
import io.github.classgraph.ClassInfoList;
|
|
||||||
import io.github.classgraph.ScanResult;
|
|
||||||
import sun.misc.Unsafe;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides methods to get instances of {@link ReflectClass}.
|
||||||
|
*/
|
||||||
public class Reflect {
|
public class Reflect {
|
||||||
|
|
||||||
private static final Unsafe sunMiscUnsafeInstance;
|
|
||||||
|
|
||||||
static {
|
|
||||||
classCache = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
|
|
||||||
try {
|
private static final Map<Class<?>, ReflectClass<?>> classCache = Collections.synchronizedMap(new HashMap<>());
|
||||||
sunMiscUnsafeInstance = (Unsafe) ofClass(Unsafe.class).field("theUnsafe")
|
|
||||||
.getStaticValue();
|
|
||||||
} catch (Exception e) {
|
/**
|
||||||
throw new RuntimeException("Cannot access to " + Unsafe.class + ".theUnsafe value.", e);
|
* Wraps the provided class into a {@link ReflectClass}.
|
||||||
}
|
* @param clazz the class to wrap.
|
||||||
}
|
* @param <T> the type of the class.
|
||||||
|
* @return a {@link ReflectClass} wrapping the provided class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static final Map<Class<?>, ReflectClass<?>> classCache;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> ReflectClass<T> ofClass(Class<T> clazz) {
|
public static <T> ReflectClass<T> ofClass(Class<T> clazz) {
|
||||||
return (ReflectClass<T>) classCache.computeIfAbsent(clazz, ReflectClass::new);
|
return (ReflectClass<T>) classCache.computeIfAbsent(clazz, ReflectClass::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the provided class into a {@link ReflectClass}.
|
||||||
|
* @param className the name of the class, passed into {@link Class#forName(String)} before using
|
||||||
|
* {@link #ofClass(Class)}.
|
||||||
|
* @return a {@link ReflectClass} wrapping the provided class.
|
||||||
|
* @throws ClassNotFoundException if the provided class was not found.
|
||||||
|
*/
|
||||||
public static ReflectClass<?> ofClass(String className) throws ClassNotFoundException {
|
public static ReflectClass<?> ofClass(String className) throws ClassNotFoundException {
|
||||||
return ofClass(Class.forName(className));
|
return ofClass(Class.forName(className));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the class of the provided object into a {@link ReflectClass}.
|
||||||
|
* @param instance the object wrom which to get the class using {@link Object#getClass()}.
|
||||||
|
* @return a {@link ReflectClass} wrapping the provided object’s class.
|
||||||
|
* @throws IllegalArgumentException if {@code instance} is null.
|
||||||
|
*/
|
||||||
public static ReflectClass<?> ofClassOfInstance(Object instance) {
|
public static ReflectClass<?> ofClassOfInstance(Object instance) {
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
throw new IllegalArgumentException("instance can't be null");
|
throw new IllegalArgumentException("instance can't be null");
|
||||||
return ofClass(instance.getClass());
|
return ofClass(instance.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private record MethodIdentifier(String methodName, Class<?>[] parameters) {
|
|
||||||
private MethodIdentifier {
|
|
||||||
Objects.requireNonNull(methodName);
|
|
||||||
parameters = (parameters == null) ? new Class<?>[0] : parameters;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
return other instanceof MethodIdentifier o
|
|
||||||
&& o.methodName.equals(methodName)
|
|
||||||
&& Arrays.equals(o.parameters, parameters);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return methodName.hashCode() ^ Arrays.hashCode(parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record ConstructorIdentifier(Class<?>[] parameters) {
|
|
||||||
private ConstructorIdentifier {
|
|
||||||
parameters = (parameters == null) ? new Class<?>[0] : parameters;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
return other instanceof ConstructorIdentifier o
|
|
||||||
&& Arrays.equals(o.parameters, parameters);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Arrays.hashCode(parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class ReflectClass<T> {
|
|
||||||
private final Class<T> clazz;
|
|
||||||
|
|
||||||
private final Map<MethodIdentifier, ReflectMethod<T>> methodCache = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
private final Map<ConstructorIdentifier, ReflectConstructor<T>> constructorCache = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
private final Map<String, ReflectField<T>> fieldCache = Collections.synchronizedMap(new HashMap<>());
|
|
||||||
|
|
||||||
private ReflectClass(Class<T> clazz) {
|
|
||||||
this.clazz = clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the class wrapped by this ReflectClass instance.
|
|
||||||
*/
|
|
||||||
public Class<T> get() {
|
|
||||||
return clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReflectMethod<T> method(MethodIdentifier key, boolean bypassFilter) throws NoSuchMethodException {
|
|
||||||
ReflectMethod<T> method = methodCache.get(key);
|
|
||||||
if (method == null) {
|
|
||||||
method = new ReflectMethod<>(this, key, bypassFilter);
|
|
||||||
methodCache.put(key, method);
|
|
||||||
}
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReflectMethod<T> method(String name, Class<?>... paramTypes) throws NoSuchMethodException {
|
|
||||||
return method(new MethodIdentifier(name, paramTypes), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReflectMethod<T> filteredMethod(String name, Class<?>... paramTypes) throws NoSuchMethodException {
|
|
||||||
return method(new MethodIdentifier(name, paramTypes), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReflectConstructor<T> constructor(ConstructorIdentifier key, boolean bypassFilter) throws NoSuchMethodException {
|
|
||||||
ReflectConstructor<T> constructor = constructorCache.get(key);
|
|
||||||
if (constructor == null) {
|
|
||||||
constructor = new ReflectConstructor<>(this, key, bypassFilter);
|
|
||||||
constructorCache.put(key, constructor);
|
|
||||||
}
|
|
||||||
return constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReflectConstructor<T> constructor(Class<?>... paramTypes) throws NoSuchMethodException {
|
|
||||||
return constructor(new ConstructorIdentifier(paramTypes), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReflectConstructor<T> filteredConstructor(Class<?>... paramTypes) throws NoSuchMethodException {
|
|
||||||
return constructor(new ConstructorIdentifier(paramTypes), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReflectField<T> field0(String name, boolean bypassFilter) throws NoSuchFieldException {
|
|
||||||
ReflectField<T> constructor = fieldCache.get(name);
|
|
||||||
if (constructor == null) {
|
|
||||||
constructor = new ReflectField<>(this, name, bypassFilter);
|
|
||||||
fieldCache.put(name, constructor);
|
|
||||||
}
|
|
||||||
return constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReflectField<T> field(String name) throws NoSuchFieldException {
|
|
||||||
return field0(name, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReflectField<T> filteredField(String name) throws NoSuchFieldException {
|
|
||||||
return field0(name, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final AtomicReference<List<Class<?>>> cachedSubclasses = new AtomicReference<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all subclasses of the current class, using the ClassGraph library.
|
|
||||||
* <p>
|
|
||||||
* If the returned list is not yet cached, or {@code forceUpdateCache} is true, then the cache is updated before
|
|
||||||
* being returned. This may take some time.
|
|
||||||
* <p>
|
|
||||||
* The ClassGraph library scan all class files in the class path, then loads those which will be returned by
|
|
||||||
* this method.
|
|
||||||
*
|
|
||||||
* @param forceUpdateCache to force the update of the cache, even if it already filled.
|
|
||||||
* @return the list of all subclasses found in all loaded class loader.
|
|
||||||
* @throws ClassGraphException if any of the worker threads throws an uncaught exception, or the scan was interrupted. (see {@link ClassGraph#scan})
|
|
||||||
* @throws IllegalArgumentException f an exception or error was thrown while trying to load any of the classes. (see {@link ClassInfoList#loadClasses()})
|
|
||||||
*/
|
|
||||||
public List<Class<? extends T>> getAllSubclasses(boolean forceUpdateCache) {
|
|
||||||
synchronized (cachedSubclasses) {
|
|
||||||
if (forceUpdateCache || cachedSubclasses.get() == null) {
|
|
||||||
try (ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) {
|
|
||||||
cachedSubclasses.set(scanResult.getSubclasses(clazz).loadClasses());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<Class<? extends T>> ret = (List<Class<? extends T>>) (List<?>) cachedSubclasses.get();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static abstract class ReflectMember<T, ID, EL, EX extends ReflectiveOperationException> {
|
|
||||||
protected final ReflectClass<T> reflectClass;
|
|
||||||
protected final ID identifier;
|
|
||||||
|
|
||||||
protected final EL cached;
|
|
||||||
|
|
||||||
protected ReflectMember(ReflectClass<T> c, ID id, boolean bypassFilter) throws EX {
|
|
||||||
reflectClass = c;
|
|
||||||
identifier = id;
|
|
||||||
cached = (bypassFilter) ? fetchFiltered() : fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected EL fetch() throws EX {
|
|
||||||
|
|
||||||
// get element in current class
|
|
||||||
try {
|
|
||||||
EL el = fetchFromClass(reflectClass.clazz);
|
|
||||||
setAccessible(el);
|
|
||||||
return el;
|
|
||||||
} catch (ReflectiveOperationException e1) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
EX ex = (EX) e1;
|
|
||||||
|
|
||||||
// get parent class
|
|
||||||
Class<? super T> superClass = reflectClass.clazz.getSuperclass();
|
|
||||||
if (superClass == null)
|
|
||||||
throw ex;
|
|
||||||
|
|
||||||
// get element in parent class (will do recursion)
|
|
||||||
try {
|
|
||||||
EL el = fetchFromReflectClass(ofClass(superClass));
|
|
||||||
setAccessible(el);
|
|
||||||
return el;
|
|
||||||
} catch (ReflectiveOperationException e2) {
|
|
||||||
ex.addSuppressed(e2);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected EL fetchFiltered() throws EX {
|
|
||||||
|
|
||||||
// get element in current class
|
|
||||||
try {
|
|
||||||
EL el = fetchFromClass(reflectClass.clazz);
|
|
||||||
setAccessible(el);
|
|
||||||
return el;
|
|
||||||
} catch (ReflectiveOperationException e1) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
EX ex = (EX) e1;
|
|
||||||
|
|
||||||
// trying to bypass filtered member
|
|
||||||
try {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
EL[] elements = (EL[]) Reflect.ofClassOfInstance(reflectClass.clazz)
|
|
||||||
.method(internalMethodNameElementArray(), boolean.class)
|
|
||||||
.invoke(reflectClass.clazz, false);
|
|
||||||
for (EL element : elements) {
|
|
||||||
if (isEqualOurElement(element)) {
|
|
||||||
// the values in the elements array have to be copied
|
|
||||||
// (using special private methods in reflection api) before using it
|
|
||||||
Object reflectionFactoryOfClazz = Reflect.ofClassOfInstance(reflectClass.clazz)
|
|
||||||
.method("getReflectionFactory")
|
|
||||||
.invoke(reflectClass.clazz);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
EL copiedElement = (EL) Reflect.ofClassOfInstance(reflectionFactoryOfClazz)
|
|
||||||
.method(internalMethodNameCopyElement(), element.getClass())
|
|
||||||
.invoke(reflectionFactoryOfClazz, element);
|
|
||||||
setAccessible(copiedElement);
|
|
||||||
return copiedElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ReflectiveOperationException e2) {
|
|
||||||
ex.addSuppressed(e2);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract EL fetchFromClass(Class<T> clazz) throws EX;
|
|
||||||
protected abstract EL fetchFromReflectClass(ReflectClass<?> rc) throws EX;
|
|
||||||
protected abstract boolean isEqualOurElement(EL el);
|
|
||||||
protected abstract String internalMethodNameElementArray();
|
|
||||||
protected abstract String internalMethodNameCopyElement();
|
|
||||||
protected abstract void setAccessible(EL el);
|
|
||||||
|
|
||||||
public EL get() {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract int getModifiers();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class ReflectField<T> extends ReflectMember<T, String, Field, NoSuchFieldException> {
|
|
||||||
|
|
||||||
/* package */ ReflectField(ReflectClass<T> c, String name, boolean bypassFilter) throws NoSuchFieldException {
|
|
||||||
super(c, name, bypassFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected Field fetchFromClass(Class<T> clazz) throws NoSuchFieldException { return clazz.getDeclaredField(identifier); }
|
|
||||||
@Override protected Field fetchFromReflectClass(ReflectClass<?> rc) throws NoSuchFieldException { return rc.field(identifier).get(); }
|
|
||||||
@Override protected boolean isEqualOurElement(Field el) { return identifier.equals(el.getName()); }
|
|
||||||
@Override protected String internalMethodNameElementArray() { return "getDeclaredFields0"; }
|
|
||||||
@Override protected String internalMethodNameCopyElement() { return "copyField"; }
|
|
||||||
@Override protected void setAccessible(Field el) { el.setAccessible(true); }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Object getValue(Object instance) throws ReflectiveOperationException {
|
|
||||||
return get().get(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getStaticValue() throws ReflectiveOperationException {
|
|
||||||
return getValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(Object instance, Object value) throws ReflectiveOperationException {
|
|
||||||
|
|
||||||
Field f = get();
|
|
||||||
if (Modifier.isFinal(f.getModifiers())) {
|
|
||||||
// if the field is final, we have to do some unsafe stuff :/
|
|
||||||
if (sunMiscUnsafeInstance != null) { // Java >= 16
|
|
||||||
// set the value of the field, directly in the memory
|
|
||||||
if (Modifier.isStatic(f.getModifiers())) {
|
|
||||||
long offset = sunMiscUnsafeInstance.staticFieldOffset(f);
|
|
||||||
sunMiscUnsafeInstance.putObject(sunMiscUnsafeInstance.staticFieldBase(f), offset, value);
|
|
||||||
} else {
|
|
||||||
long offset = sunMiscUnsafeInstance.objectFieldOffset(f);
|
|
||||||
sunMiscUnsafeInstance.putObject(instance, offset, value);
|
|
||||||
}
|
|
||||||
} else { // Java < 16
|
|
||||||
// change the modifier in the Field instance so the method #set(instance, value) doesn’t throw an exception
|
|
||||||
int modifiers = f.getModifiers();
|
|
||||||
if (Modifier.isFinal(modifiers)) {
|
|
||||||
ofClass(Field.class).field("modifiers").fetchFiltered().set(f, modifiers & ~Modifier.FINAL);
|
|
||||||
}
|
|
||||||
f.set(instance, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else { // not final value
|
|
||||||
f.set(instance, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStaticValue(Object value) throws ReflectiveOperationException {
|
|
||||||
setValue(null, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getModifiers() {
|
|
||||||
return get().getModifiers();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class ReflectMethod<T> extends ReflectMember<T, MethodIdentifier, Method, NoSuchMethodException> {
|
|
||||||
|
|
||||||
/* package */ ReflectMethod(ReflectClass<T> c, MethodIdentifier methodId, boolean bypassFilter) throws NoSuchMethodException {
|
|
||||||
super(c, methodId, bypassFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected Method fetchFromClass(Class<T> clazz) throws NoSuchMethodException { return clazz.getDeclaredMethod(identifier.methodName, identifier.parameters); }
|
|
||||||
@Override protected Method fetchFromReflectClass(ReflectClass<?> rc) throws NoSuchMethodException { return rc.method(identifier, false).get(); }
|
|
||||||
@Override protected boolean isEqualOurElement(Method el) { return identifier.methodName.equals(el.getName()) && Arrays.equals(identifier.parameters, el.getParameterTypes()); }
|
|
||||||
@Override protected String internalMethodNameElementArray() { return "getDeclaredMethods0"; }
|
|
||||||
@Override protected String internalMethodNameCopyElement() { return "copyMethod"; }
|
|
||||||
@Override protected void setAccessible(Method el) { el.setAccessible(true); }
|
|
||||||
|
|
||||||
public Object invoke(Object instance, Object... values) throws ReflectiveOperationException {
|
|
||||||
return get().invoke(instance, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object invokeStatic(Object... values) throws ReflectiveOperationException {
|
|
||||||
return invoke(null, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getModifiers() {
|
|
||||||
return get().getModifiers();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class ReflectConstructor<T> extends ReflectMember<T, ConstructorIdentifier, Constructor<T>, NoSuchMethodException> {
|
|
||||||
|
|
||||||
/* package */ ReflectConstructor(ReflectClass<T> c, ConstructorIdentifier constructorId, boolean bypassFilter) throws NoSuchMethodException {
|
|
||||||
super(c, constructorId, bypassFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override since we don't want to recursively search for a constructor
|
|
||||||
@Override
|
|
||||||
protected Constructor<T> fetch() throws NoSuchMethodException {
|
|
||||||
Constructor<T> el = fetchFromClass(reflectClass.clazz);
|
|
||||||
setAccessible(el);
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected Constructor<T> fetchFromClass(Class<T> clazz) throws NoSuchMethodException { return clazz.getDeclaredConstructor(identifier.parameters); }
|
|
||||||
@Override protected Constructor<T> fetchFromReflectClass(ReflectClass<?> rc) { throw new UnsupportedOperationException(); }
|
|
||||||
@Override protected boolean isEqualOurElement(Constructor<T> el) { return Arrays.equals(identifier.parameters, el.getParameterTypes()); }
|
|
||||||
@Override protected String internalMethodNameElementArray() { return "getDeclaredConstructors0"; }
|
|
||||||
@Override protected String internalMethodNameCopyElement() { return "copyConstructor"; }
|
|
||||||
@Override protected void setAccessible(Constructor<T> el) { el.setAccessible(true); }
|
|
||||||
|
|
||||||
public T instanciate(Object... values) throws ReflectiveOperationException {
|
|
||||||
return get().newInstance(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getModifiers() {
|
|
||||||
return get().getModifiers();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,185 @@
|
|||||||
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import io.github.classgraph.ClassGraph;
|
||||||
|
import io.github.classgraph.ClassGraphException;
|
||||||
|
import io.github.classgraph.ClassInfoList;
|
||||||
|
import io.github.classgraph.ScanResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper of a {@link Class} with less verbose access to methods and properties.
|
||||||
|
* @param <T> the type of the wrapped class.
|
||||||
|
*/
|
||||||
|
public class ReflectClass<T> {
|
||||||
|
private final Class<T> clazz;
|
||||||
|
|
||||||
|
private final Map<MethodIdentifier, ReflectMethod<T>> methodCache = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
private final Map<ConstructorIdentifier, ReflectConstructor<T>> constructorCache = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
private final Map<String, ReflectField<T>> fieldCache = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
ReflectClass(Class<T> clazz) {
|
||||||
|
this.clazz = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the class wrapped by this {@link ReflectClass} instance.
|
||||||
|
* @return the class wrapped by this {@link ReflectClass} instance.
|
||||||
|
*/
|
||||||
|
public Class<T> get() {
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ReflectMethod<T> method(MethodIdentifier key, boolean bypassFilter) throws NoSuchMethodException {
|
||||||
|
ReflectMethod<T> method = methodCache.get(key);
|
||||||
|
if (method == null) {
|
||||||
|
method = new ReflectMethod<>(this, key, bypassFilter);
|
||||||
|
methodCache.put(key, method);
|
||||||
|
}
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link ReflectMethod} wrapping the requested {@link Method}.
|
||||||
|
* @param name the method name.
|
||||||
|
* @param paramTypes the types of the method parameters.
|
||||||
|
* @return a {@link ReflectMethod} wrapping the requested {@link Method}.
|
||||||
|
* @throws NoSuchMethodException if the requested method doesn’t exists in the wrapped class.
|
||||||
|
*/
|
||||||
|
public ReflectMethod<T> method(String name, Class<?>... paramTypes) throws NoSuchMethodException {
|
||||||
|
return method(new MethodIdentifier(name, paramTypes), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link ReflectMethod} wrapping the requested {@link Method}, bypassing some internal filtering in the
|
||||||
|
* {@link Class} implementation.
|
||||||
|
* @param name the method name.
|
||||||
|
* @param paramTypes the types of the method parameters.
|
||||||
|
* @return a {@link ReflectMethod} wrapping the requested {@link Method}.
|
||||||
|
* @throws NoSuchMethodException if the requested method doesn’t exists in the wrapped class.
|
||||||
|
*/
|
||||||
|
public ReflectMethod<T> filteredMethod(String name, Class<?>... paramTypes) throws NoSuchMethodException {
|
||||||
|
return method(new MethodIdentifier(name, paramTypes), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ReflectConstructor<T> constructor(ConstructorIdentifier key, boolean bypassFilter) throws NoSuchMethodException {
|
||||||
|
ReflectConstructor<T> constructor = constructorCache.get(key);
|
||||||
|
if (constructor == null) {
|
||||||
|
constructor = new ReflectConstructor<>(this, key, bypassFilter);
|
||||||
|
constructorCache.put(key, constructor);
|
||||||
|
}
|
||||||
|
return constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link ReflectConstructor} wrapping the requested {@link Constructor}.
|
||||||
|
* @param paramTypes the types of the constructor parameters.
|
||||||
|
* @return a {@link ReflectConstructor} wrapping the requested {@link Constructor}.
|
||||||
|
* @throws NoSuchMethodException if the requested constructor doesn’t exists in the wrapped class.
|
||||||
|
*/
|
||||||
|
public ReflectConstructor<T> constructor(Class<?>... paramTypes) throws NoSuchMethodException {
|
||||||
|
return constructor(new ConstructorIdentifier(paramTypes), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link ReflectConstructor} wrapping the requested {@link Constructor}, bypassing some internal
|
||||||
|
* filtering in the {@link Class} implementation.
|
||||||
|
* @param paramTypes the types of the constructor parameters.
|
||||||
|
* @return a {@link ReflectConstructor} wrapping the requested {@link Constructor}.
|
||||||
|
* @throws NoSuchMethodException if the requested constructor doesn’t exists in the wrapped class.
|
||||||
|
*/
|
||||||
|
public ReflectConstructor<T> filteredConstructor(Class<?>... paramTypes) throws NoSuchMethodException {
|
||||||
|
return constructor(new ConstructorIdentifier(paramTypes), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ReflectField<T> field0(String name, boolean bypassFilter) throws NoSuchFieldException {
|
||||||
|
ReflectField<T> constructor = fieldCache.get(name);
|
||||||
|
if (constructor == null) {
|
||||||
|
constructor = new ReflectField<>(this, name, bypassFilter);
|
||||||
|
fieldCache.put(name, constructor);
|
||||||
|
}
|
||||||
|
return constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link ReflectField} wrapping the requested {@link Field}.
|
||||||
|
* @param name the name of the field.
|
||||||
|
* @return a {@link ReflectField} wrapping the requested {@link Field}.
|
||||||
|
* @throws NoSuchFieldException if the requested field doesn’t exists in the wrapped class.
|
||||||
|
*/
|
||||||
|
public ReflectField<T> field(String name) throws NoSuchFieldException {
|
||||||
|
return field0(name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link ReflectField} wrapping the requested {@link Field}, bypassing some internal filtering in the
|
||||||
|
* {@link Class} implementation.
|
||||||
|
* @param name the name of the field.
|
||||||
|
* @return a {@link ReflectField} wrapping the requested {@link Field}.
|
||||||
|
* @throws NoSuchFieldException if the requested field doesn’t exists in the wrapped class.
|
||||||
|
*/
|
||||||
|
public ReflectField<T> filteredField(String name) throws NoSuchFieldException {
|
||||||
|
return field0(name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private final AtomicReference<List<Class<?>>> cachedSubclasses = new AtomicReference<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all subclasses of the current class, using the ClassGraph library.
|
||||||
|
* <p>
|
||||||
|
* If the returned list is not yet cached, or {@code forceUpdateCache} is true, then the cache is updated before
|
||||||
|
* being returned. This may take some time.
|
||||||
|
* <p>
|
||||||
|
* The ClassGraph library scan all class files in the class path, then loads those which will be returned by
|
||||||
|
* this method.
|
||||||
|
*
|
||||||
|
* @param forceUpdateCache to force the update of the cache, even if it already filled.
|
||||||
|
* @return the list of all subclasses found in all loaded class loader.
|
||||||
|
* @throws ClassGraphException if any of the worker threads throws an uncaught exception, or the scan was interrupted. (see {@link ClassGraph#scan})
|
||||||
|
* @throws IllegalArgumentException f an exception or error was thrown while trying to load any of the classes. (see {@link ClassInfoList#loadClasses()})
|
||||||
|
*/
|
||||||
|
public List<Class<? extends T>> getAllSubclasses(boolean forceUpdateCache) {
|
||||||
|
synchronized (cachedSubclasses) {
|
||||||
|
if (forceUpdateCache || cachedSubclasses.get() == null) {
|
||||||
|
try (ScanResult scanResult = new ClassGraph().enableClassInfo().ignoreClassVisibility().scan()) {
|
||||||
|
cachedSubclasses.set(scanResult.getSubclasses(clazz).loadClasses());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Class<? extends T>> ret = (List<Class<? extends T>>) (List<?>) cachedSubclasses.get();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for a class {@link Constructor}.
|
||||||
|
* @param <T> the type of the class declaring the wrapped constructor.
|
||||||
|
*/
|
||||||
|
public final class ReflectConstructor<T> extends ReflectMember<T, ConstructorIdentifier, Constructor<T>, NoSuchMethodException> {
|
||||||
|
|
||||||
|
/* package */ ReflectConstructor(ReflectClass<T> c, ConstructorIdentifier constructorId, boolean bypassFilter) throws NoSuchMethodException {
|
||||||
|
super(c, constructorId, bypassFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override since we don't want to recursively search for a constructor
|
||||||
|
@Override
|
||||||
|
protected Constructor<T> fetch() throws NoSuchMethodException {
|
||||||
|
Constructor<T> el = fetchFromClass(reflectClass.get());
|
||||||
|
setAccessible(el);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Constructor<T> fetchFromClass(Class<T> clazz) throws NoSuchMethodException {
|
||||||
|
return clazz.getDeclaredConstructor(identifier.parameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Constructor<T> fetchFromReflectClass(ReflectClass<?> rc) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isEqualOurElement(Constructor<T> el) {
|
||||||
|
return Arrays.equals(identifier.parameters(), el.getParameterTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String internalMethodNameElementArray() {
|
||||||
|
return "getDeclaredConstructors0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String internalMethodNameCopyElement() {
|
||||||
|
return "copyConstructor";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke this constructor to create a new instance of the declaring class of this constructor.
|
||||||
|
* @param values the parameters used for the constructor call.
|
||||||
|
* @return the newly created instance.
|
||||||
|
* @throws IllegalAccessException if the wrapped Constructor object is enforcing Java language access control and
|
||||||
|
* the underlying constructor is inaccessible. Note that this
|
||||||
|
* {@link ReflectConstructor} automatically sets the {@link Constructor}’s accessible
|
||||||
|
* flag to true.
|
||||||
|
* @throws IllegalArgumentException if there is any problem with the constructor parameters (wrong number, wrong
|
||||||
|
* type, ...).
|
||||||
|
* @throws InstantiationException if the declaring class of this constructor is an abstract class.
|
||||||
|
* @throws InvocationTargetException if the called constructor throws an exception.
|
||||||
|
* @see Constructor#newInstance(Object...)
|
||||||
|
*/
|
||||||
|
public T instanciate(Object... values) throws InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||||
|
return get().newInstance(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for a class {@link Field}.
|
||||||
|
* @param <T> the type of the class declaring the wrapped field.
|
||||||
|
*/
|
||||||
|
public final class ReflectField<T> extends ReflectMember<T, String, Field, NoSuchFieldException> {
|
||||||
|
|
||||||
|
private static sun.misc.Unsafe sunMiscUnsafeInstance;
|
||||||
|
private static Field modifiersFieldInFieldClass;
|
||||||
|
static {
|
||||||
|
RuntimeException ex = null;
|
||||||
|
try {
|
||||||
|
sunMiscUnsafeInstance = Runtime.version().feature() >= 16
|
||||||
|
? (sun.misc.Unsafe) Reflect.ofClass("sun.misc.Unsafe")
|
||||||
|
.field("theUnsafe")
|
||||||
|
.getStaticValue()
|
||||||
|
: null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
ex = new RuntimeException("Cannot access to sun.misc.Unsafe.theUnsafe value.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
modifiersFieldInFieldClass = Reflect.ofClass(Field.class).field("modifiers").fetchFiltered();
|
||||||
|
} catch (Exception e) {
|
||||||
|
RuntimeException newEx = new RuntimeException("Cannot access " + Field.class + ".modifiers field.", e);
|
||||||
|
if (ex != null)
|
||||||
|
newEx.addSuppressed(ex);
|
||||||
|
ex = newEx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex != null)
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ ReflectField(ReflectClass<T> c, String name, boolean bypassFilter) throws NoSuchFieldException {
|
||||||
|
super(c, name, bypassFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/* package */ Field fetchFromClass(Class<T> clazz) throws NoSuchFieldException {
|
||||||
|
return clazz.getDeclaredField(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/* package */ Field fetchFromReflectClass(ReflectClass<?> rc) throws NoSuchFieldException {
|
||||||
|
return rc.field(identifier).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/* package */ boolean isEqualOurElement(Field el) {
|
||||||
|
return identifier.equals(el.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/* package */ String internalMethodNameElementArray() {
|
||||||
|
return "getDeclaredFields0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/* package */ String internalMethodNameCopyElement() {
|
||||||
|
return "copyField";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of this field, in the provided instance.
|
||||||
|
* @param instance the instance in which to get the value of the field.
|
||||||
|
* @return the value of this field, in the provided instance.
|
||||||
|
* @throws IllegalAccessException if the wrapped Field object is enforcing Java language access control and the
|
||||||
|
* underlying field is inaccessible. Note that this {@link ReflectField}
|
||||||
|
* automatically sets the {@link Field}’s accessible flag to true.
|
||||||
|
* @throws IllegalArgumentException if the specified instance is not an instance of the class or interface declaring
|
||||||
|
* the wrapped field (or a subclass or implementor thereof).
|
||||||
|
* @throws NullPointerException if the specified instance is null and the field is an instance field.
|
||||||
|
* @see Field#get(Object)
|
||||||
|
*/
|
||||||
|
public Object getValue(Object instance) throws IllegalAccessException {
|
||||||
|
return get().get(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of this static field.
|
||||||
|
* @return the value of this static field.
|
||||||
|
* @throws IllegalAccessException if the wrapped Field object is enforcing Java language access control and the
|
||||||
|
* underlying field is inaccessible. Note that this {@link ReflectField}
|
||||||
|
* automatically sets the {@link Field}’s accessible flag to true.
|
||||||
|
* @throws NullPointerException if the wrapped {@link Field} is actually an instance field. In this case,
|
||||||
|
* {@link #getValue(Object)} should be called instead with a non-null parameter.
|
||||||
|
* @see Field#get(Object)
|
||||||
|
*/
|
||||||
|
public Object getStaticValue() throws IllegalAccessException {
|
||||||
|
return getValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of this field, in the provided instance.
|
||||||
|
* @param instance the instance in which to set the value of the field.
|
||||||
|
* @param value the new value for this field.
|
||||||
|
* @throws IllegalAccessException if the wrapped Field object is enforcing Java language access control and the
|
||||||
|
* underlying field is inaccessible. Note that this {@link ReflectField}
|
||||||
|
* automatically sets the {@link Field}’s accessible flag to true.
|
||||||
|
* @throws IllegalArgumentException if the specified instance is not an instance of the class or interface declaring
|
||||||
|
* the wrapped field (or a subclass or implementor thereof).
|
||||||
|
* @throws NullPointerException if the specified instance is null and the field is an instance field.
|
||||||
|
* @see Field#set(Object, Object)
|
||||||
|
*/
|
||||||
|
public void setValue(Object instance, Object value) throws IllegalAccessException {
|
||||||
|
|
||||||
|
Field f = get();
|
||||||
|
int realModifiers = f.getModifiers();
|
||||||
|
if (Modifier.isFinal(realModifiers)) {
|
||||||
|
// if the field is final, we have to do some unsafe stuff :/
|
||||||
|
if (sunMiscUnsafeInstance != null) { // Java >= 16
|
||||||
|
// set the value of the field, directly in the memory
|
||||||
|
if (Modifier.isStatic(realModifiers)) {
|
||||||
|
long offset = sunMiscUnsafeInstance.staticFieldOffset(f);
|
||||||
|
sunMiscUnsafeInstance.putObject(sunMiscUnsafeInstance.staticFieldBase(f), offset, value);
|
||||||
|
} else {
|
||||||
|
long offset = sunMiscUnsafeInstance.objectFieldOffset(f);
|
||||||
|
sunMiscUnsafeInstance.putObject(instance, offset, value);
|
||||||
|
}
|
||||||
|
} else { // Java < 16
|
||||||
|
// change the modifier in the Field instance so the method #set(instance, value) doesn’t throw an exception
|
||||||
|
try {
|
||||||
|
modifiersFieldInFieldClass.set(f, realModifiers & ~Modifier.FINAL);
|
||||||
|
f.set(instance, value);
|
||||||
|
} finally {
|
||||||
|
modifiersFieldInFieldClass.set(f, realModifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // not final value
|
||||||
|
f.set(instance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of this static field.
|
||||||
|
* @param value the new value for this field.
|
||||||
|
* @throws IllegalAccessException if the wrapped Field object is enforcing Java language access control and the
|
||||||
|
* underlying field is inaccessible. Note that this {@link ReflectField}
|
||||||
|
* automatically sets the {@link Field}’s accessible flag to true.
|
||||||
|
* @throws NullPointerException if the wrapped {@link Field} is actually an instance field. In this case,
|
||||||
|
* {@link #setValue(Object, Object)} should be called instead with a non-null parameter.
|
||||||
|
* @see Field#set(Object, Object)
|
||||||
|
*/
|
||||||
|
public void setStaticValue(Object value) throws IllegalAccessException {
|
||||||
|
setValue(null, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.AccessibleObject;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract wrapper for a class member (field, method or constructor).
|
||||||
|
* @param <T> the type of the class declaring the wrapped member.
|
||||||
|
* @param <ID> type of object uniquely identifying the member into the class.
|
||||||
|
* @param <EL> the type of the class member (like {@link Field}, {@link Method} or {@link Constructor})
|
||||||
|
* @param <EX> the type of exception thrown by the {@link Class} method responsible to get the member instance (like
|
||||||
|
* {@link NoSuchFieldException} or {@link NoSuchMethodException}).
|
||||||
|
*/
|
||||||
|
public sealed abstract class ReflectMember<T, ID, EL extends AccessibleObject & Member, EX extends ReflectiveOperationException>
|
||||||
|
permits ReflectField, ReflectMethod, ReflectConstructor {
|
||||||
|
|
||||||
|
/* package */ final ReflectClass<T> reflectClass;
|
||||||
|
/* package */ final ID identifier;
|
||||||
|
private final EL member;
|
||||||
|
|
||||||
|
/* package */ ReflectMember(ReflectClass<T> c, ID id, boolean bypassFilter) throws EX {
|
||||||
|
reflectClass = c;
|
||||||
|
identifier = id;
|
||||||
|
member = (bypassFilter) ? fetchFiltered() : fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* package */ EL fetch() throws EX {
|
||||||
|
|
||||||
|
// get element in current class
|
||||||
|
try {
|
||||||
|
EL el = fetchFromClass(reflectClass.get());
|
||||||
|
setAccessible(el);
|
||||||
|
return el;
|
||||||
|
} catch (ReflectiveOperationException e1) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
EX ex = (EX) e1;
|
||||||
|
|
||||||
|
// get parent class
|
||||||
|
Class<? super T> superClass = reflectClass.get().getSuperclass();
|
||||||
|
if (superClass == null)
|
||||||
|
throw ex;
|
||||||
|
|
||||||
|
// get element in parent class (will do recursion)
|
||||||
|
try {
|
||||||
|
EL el = fetchFromReflectClass(Reflect.ofClass(superClass));
|
||||||
|
setAccessible(el);
|
||||||
|
return el;
|
||||||
|
} catch (ReflectiveOperationException e2) {
|
||||||
|
ex.addSuppressed(e2);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ EL fetchFiltered() throws EX {
|
||||||
|
|
||||||
|
// get element in current class
|
||||||
|
try {
|
||||||
|
EL el = fetchFromClass(reflectClass.get());
|
||||||
|
setAccessible(el);
|
||||||
|
return el;
|
||||||
|
} catch (ReflectiveOperationException e1) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
EX ex = (EX) e1;
|
||||||
|
|
||||||
|
// trying to bypass filtered member
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
EL[] elements = (EL[]) Reflect.ofClassOfInstance(reflectClass.get())
|
||||||
|
.method(internalMethodNameElementArray(), boolean.class)
|
||||||
|
.invoke(reflectClass.get(), false);
|
||||||
|
for (EL element : elements) {
|
||||||
|
if (isEqualOurElement(element)) {
|
||||||
|
// the values in the elements array have to be copied
|
||||||
|
// (using special private methods in reflection api) before using it
|
||||||
|
Object reflectionFactoryOfClazz = Reflect.ofClassOfInstance(reflectClass.get())
|
||||||
|
.method("getReflectionFactory")
|
||||||
|
.invoke(reflectClass.get());
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
EL copiedElement = (EL) Reflect.ofClassOfInstance(reflectionFactoryOfClazz)
|
||||||
|
.method(internalMethodNameCopyElement(), element.getClass())
|
||||||
|
.invoke(reflectionFactoryOfClazz, element);
|
||||||
|
setAccessible(copiedElement);
|
||||||
|
return copiedElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException e2) {
|
||||||
|
ex.addSuppressed(e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ abstract EL fetchFromClass(Class<T> clazz) throws EX;
|
||||||
|
|
||||||
|
/* package */ abstract EL fetchFromReflectClass(ReflectClass<?> rc) throws EX;
|
||||||
|
|
||||||
|
/* package */ abstract boolean isEqualOurElement(EL el);
|
||||||
|
|
||||||
|
/* package */ abstract String internalMethodNameElementArray();
|
||||||
|
|
||||||
|
/* package */ abstract String internalMethodNameCopyElement();
|
||||||
|
|
||||||
|
/* package */ void setAccessible(EL el) {
|
||||||
|
el.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the wrapped class member.
|
||||||
|
* @return the wrapped class member.
|
||||||
|
*/
|
||||||
|
public EL get() {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the modifiers of the wrapped class member.
|
||||||
|
* @return the modifiers of the wrapped class member.
|
||||||
|
* @see Field#getModifiers()
|
||||||
|
* @see Method#getModifiers()
|
||||||
|
* @see Constructor#getModifiers()
|
||||||
|
*/
|
||||||
|
public int getModifiers() {
|
||||||
|
return get().getModifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package fr.pandacube.lib.reflect;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for a class {@link Method}.
|
||||||
|
* @param <T> the type of the class declaring the wrapped method.
|
||||||
|
*/
|
||||||
|
public final class ReflectMethod<T> extends ReflectMember<T, MethodIdentifier, Method, NoSuchMethodException> {
|
||||||
|
|
||||||
|
/* package */ ReflectMethod(ReflectClass<T> c, MethodIdentifier methodId, boolean bypassFilter) throws NoSuchMethodException {
|
||||||
|
super(c, methodId, bypassFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Method fetchFromClass(Class<T> clazz) throws NoSuchMethodException {
|
||||||
|
return clazz.getDeclaredMethod(identifier.methodName(), identifier.parameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Method fetchFromReflectClass(ReflectClass<?> rc) throws NoSuchMethodException {
|
||||||
|
return rc.method(identifier, false).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isEqualOurElement(Method el) {
|
||||||
|
return identifier.methodName().equals(el.getName()) && Arrays.equals(identifier.parameters(), el.getParameterTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String internalMethodNameElementArray() {
|
||||||
|
return "getDeclaredMethods0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String internalMethodNameCopyElement() {
|
||||||
|
return "copyMethod";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes this method on the specified instance with the specified parameters.
|
||||||
|
* @param instance the instance on which to call the method.
|
||||||
|
* @param values the parameters used for the method call.
|
||||||
|
* @return the eventual return value of the method call.
|
||||||
|
* @throws IllegalAccessException if the wrapped Method object is enforcing Java language access control and the
|
||||||
|
* underlying method is inaccessible. Note that this {@link ReflectMethod}
|
||||||
|
* automatically sets the {@link Method}’s accessible flag to true.
|
||||||
|
* @throws IllegalArgumentException if the specified instance is not an instance of the class or interface declaring
|
||||||
|
* the wrapped method (or a subclass or implementor thereof), or if there is any
|
||||||
|
* problem with the method parameters (wrong number, wrong type, ...).
|
||||||
|
* @throws NullPointerException if the specified instance is null and the field is an instance field.
|
||||||
|
* @throws InvocationTargetException if the called method throws an exception.
|
||||||
|
* @see Method#invoke(Object, Object...)
|
||||||
|
*/
|
||||||
|
public Object invoke(Object instance, Object... values) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
return get().invoke(instance, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes this static method with the specified parameters.
|
||||||
|
* @param values the parameters used for the method call.
|
||||||
|
* @return the eventual return value of the method call.
|
||||||
|
* @throws IllegalAccessException if the wrapped Method object is enforcing Java language access control and the
|
||||||
|
* underlying method is inaccessible. Note that this {@link ReflectMethod}
|
||||||
|
* automatically sets the {@link Method}’s accessible flag to true.
|
||||||
|
* @throws IllegalArgumentException if there is any problem with the method parameters (wrong number, wrong type,
|
||||||
|
* ...).
|
||||||
|
* @throws NullPointerException if the wrapped {@link Method} is actually an instance method. In this case,
|
||||||
|
* {@link #invoke(Object, Object...)} should be called instead with a non-null first
|
||||||
|
* parameter.
|
||||||
|
* @throws InvocationTargetException if the called method throws an exception.
|
||||||
|
* @see Method#invoke(Object, Object...)
|
||||||
|
*/
|
||||||
|
public Object invokeStatic(Object... values) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
return invoke(null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user