From eea6d2b5b23e1681c26d900864c1ca4bbb108cbe Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Thu, 28 Jul 2022 01:05:35 +0200 Subject: [PATCH] pandalib-reflect Refactor + javadoc --- pandalib-reflect/pom.xml | 7 +- .../lib/reflect/ConstructorIdentifier.java | 20 + .../lib/reflect/MethodIdentifier.java | 23 + .../fr/pandacube/lib/reflect/Reflect.java | 468 +----------------- .../pandacube/lib/reflect/ReflectClass.java | 185 +++++++ .../lib/reflect/ReflectConstructor.java | 68 +++ .../pandacube/lib/reflect/ReflectField.java | 153 ++++++ .../pandacube/lib/reflect/ReflectMember.java | 132 +++++ .../pandacube/lib/reflect/ReflectMethod.java | 80 +++ 9 files changed, 694 insertions(+), 442 deletions(-) create mode 100644 pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ConstructorIdentifier.java create mode 100644 pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/MethodIdentifier.java create mode 100644 pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectClass.java create mode 100644 pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectConstructor.java create mode 100644 pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectField.java create mode 100644 pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMember.java create mode 100644 pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMethod.java diff --git a/pandalib-reflect/pom.xml b/pandalib-reflect/pom.xml index fb9f209..338d83b 100644 --- a/pandalib-reflect/pom.xml +++ b/pandalib-reflect/pom.xml @@ -47,13 +47,18 @@ META-INF/*.SF META-INF/*.DSA META-INF/*.RSA + META-INF/MANIFEST.MF io.github.classgraph - fr.pandacube.shaded.classgraph + fr.pandacube.lib.reflect.shaded.classgraph + + + nonapi.io.github.classgraph + fr.pandacube.lib.reflect.shaded.classgraph.nonapi diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ConstructorIdentifier.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ConstructorIdentifier.java new file mode 100644 index 0000000..ef75a50 --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ConstructorIdentifier.java @@ -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); + } +} diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/MethodIdentifier.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/MethodIdentifier.java new file mode 100644 index 0000000..530a80a --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/MethodIdentifier.java @@ -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); + } +} diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/Reflect.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/Reflect.java index 1449dbc..54c030d 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/Reflect.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/Reflect.java @@ -1,465 +1,51 @@ 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.HashMap; -import java.util.List; 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 { - - private static final Unsafe sunMiscUnsafeInstance; - static { - classCache = Collections.synchronizedMap(new HashMap<>()); - try { - sunMiscUnsafeInstance = (Unsafe) ofClass(Unsafe.class).field("theUnsafe") - .getStaticValue(); - } catch (Exception e) { - throw new RuntimeException("Cannot access to " + Unsafe.class + ".theUnsafe value.", e); - } - } - - - - - - - - - private static final Map, ReflectClass> classCache; - + private static final Map, ReflectClass> classCache = Collections.synchronizedMap(new HashMap<>()); + + + /** + * Wraps the provided class into a {@link ReflectClass}. + * @param clazz the class to wrap. + * @param the type of the class. + * @return a {@link ReflectClass} wrapping the provided class. + */ @SuppressWarnings("unchecked") public static ReflectClass ofClass(Class clazz) { return (ReflectClass) 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 { 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) { if (instance == null) throw new IllegalArgumentException("instance can't be null"); 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 { - private final Class clazz; - private final Map> methodCache = Collections.synchronizedMap(new HashMap<>()); - private final Map> constructorCache = Collections.synchronizedMap(new HashMap<>()); - private final Map> fieldCache = Collections.synchronizedMap(new HashMap<>()); - - private ReflectClass(Class clazz) { - this.clazz = clazz; - } - - /** - * Returns the class wrapped by this ReflectClass instance. - */ - public Class get() { - return clazz; - } - - private ReflectMethod method(MethodIdentifier key, boolean bypassFilter) throws NoSuchMethodException { - ReflectMethod method = methodCache.get(key); - if (method == null) { - method = new ReflectMethod<>(this, key, bypassFilter); - methodCache.put(key, method); - } - return method; - } - - public ReflectMethod method(String name, Class... paramTypes) throws NoSuchMethodException { - return method(new MethodIdentifier(name, paramTypes), false); - } - - public ReflectMethod filteredMethod(String name, Class... paramTypes) throws NoSuchMethodException { - return method(new MethodIdentifier(name, paramTypes), true); - } - - private ReflectConstructor constructor(ConstructorIdentifier key, boolean bypassFilter) throws NoSuchMethodException { - ReflectConstructor constructor = constructorCache.get(key); - if (constructor == null) { - constructor = new ReflectConstructor<>(this, key, bypassFilter); - constructorCache.put(key, constructor); - } - return constructor; - } - - public ReflectConstructor constructor(Class... paramTypes) throws NoSuchMethodException { - return constructor(new ConstructorIdentifier(paramTypes), false); - } - - public ReflectConstructor filteredConstructor(Class... paramTypes) throws NoSuchMethodException { - return constructor(new ConstructorIdentifier(paramTypes), true); - } - - private ReflectField field0(String name, boolean bypassFilter) throws NoSuchFieldException { - ReflectField constructor = fieldCache.get(name); - if (constructor == null) { - constructor = new ReflectField<>(this, name, bypassFilter); - fieldCache.put(name, constructor); - } - return constructor; - } - - public ReflectField field(String name) throws NoSuchFieldException { - return field0(name, false); - } - - public ReflectField filteredField(String name) throws NoSuchFieldException { - return field0(name, true); - } - - private final AtomicReference>> cachedSubclasses = new AtomicReference<>(); - - /** - * Get all subclasses of the current class, using the ClassGraph library. - *

- * 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. - *

- * 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> 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> ret = (List>) (List) cachedSubclasses.get(); - return ret; - } - - } - } - - - - - - - - - - - - - - - - - - - - - - - - public static abstract class ReflectMember { - protected final ReflectClass reflectClass; - protected final ID identifier; - - protected final EL cached; - - protected ReflectMember(ReflectClass 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 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 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 extends ReflectMember { - - /* package */ ReflectField(ReflectClass c, String name, boolean bypassFilter) throws NoSuchFieldException { - super(c, name, bypassFilter); - } - - @Override protected Field fetchFromClass(Class 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 extends ReflectMember { - - /* package */ ReflectMethod(ReflectClass c, MethodIdentifier methodId, boolean bypassFilter) throws NoSuchMethodException { - super(c, methodId, bypassFilter); - } - - @Override protected Method fetchFromClass(Class 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 extends ReflectMember, NoSuchMethodException> { - - /* package */ ReflectConstructor(ReflectClass 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 fetch() throws NoSuchMethodException { - Constructor el = fetchFromClass(reflectClass.clazz); - setAccessible(el); - return el; - } - - @Override protected Constructor fetchFromClass(Class clazz) throws NoSuchMethodException { return clazz.getDeclaredConstructor(identifier.parameters); } - @Override protected Constructor fetchFromReflectClass(ReflectClass rc) { throw new UnsupportedOperationException(); } - @Override protected boolean isEqualOurElement(Constructor 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 el) { el.setAccessible(true); } - - public T instanciate(Object... values) throws ReflectiveOperationException { - return get().newInstance(values); - } - - @Override - public int getModifiers() { - return get().getModifiers(); - } - - } - - - } diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectClass.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectClass.java new file mode 100644 index 0000000..74ed707 --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectClass.java @@ -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 the type of the wrapped class. + */ +public class ReflectClass { + private final Class clazz; + + private final Map> methodCache = Collections.synchronizedMap(new HashMap<>()); + private final Map> constructorCache = Collections.synchronizedMap(new HashMap<>()); + private final Map> fieldCache = Collections.synchronizedMap(new HashMap<>()); + + ReflectClass(Class clazz) { + this.clazz = clazz; + } + + /** + * Returns the class wrapped by this {@link ReflectClass} instance. + * @return the class wrapped by this {@link ReflectClass} instance. + */ + public Class get() { + return clazz; + } + + + + + + + + ReflectMethod method(MethodIdentifier key, boolean bypassFilter) throws NoSuchMethodException { + ReflectMethod 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 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 filteredMethod(String name, Class... paramTypes) throws NoSuchMethodException { + return method(new MethodIdentifier(name, paramTypes), true); + } + + + + + + + private ReflectConstructor constructor(ConstructorIdentifier key, boolean bypassFilter) throws NoSuchMethodException { + ReflectConstructor 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 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 filteredConstructor(Class... paramTypes) throws NoSuchMethodException { + return constructor(new ConstructorIdentifier(paramTypes), true); + } + + + + + + + private ReflectField field0(String name, boolean bypassFilter) throws NoSuchFieldException { + ReflectField 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 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 filteredField(String name) throws NoSuchFieldException { + return field0(name, true); + } + + + + + + + + + + private final AtomicReference>> cachedSubclasses = new AtomicReference<>(); + + /** + * Get all subclasses of the current class, using the ClassGraph library. + *

+ * 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. + *

+ * 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> 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> ret = (List>) (List) cachedSubclasses.get(); + return ret; + } + + } +} diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectConstructor.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectConstructor.java new file mode 100644 index 0000000..3e46103 --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectConstructor.java @@ -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 the type of the class declaring the wrapped constructor. + */ +public final class ReflectConstructor extends ReflectMember, NoSuchMethodException> { + + /* package */ ReflectConstructor(ReflectClass 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 fetch() throws NoSuchMethodException { + Constructor el = fetchFromClass(reflectClass.get()); + setAccessible(el); + return el; + } + + @Override + protected Constructor fetchFromClass(Class clazz) throws NoSuchMethodException { + return clazz.getDeclaredConstructor(identifier.parameters()); + } + + @Override + protected Constructor fetchFromReflectClass(ReflectClass rc) { + throw new UnsupportedOperationException(); + } + + @Override + protected boolean isEqualOurElement(Constructor 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); + } + +} diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectField.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectField.java new file mode 100644 index 0000000..2d4eebc --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectField.java @@ -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 the type of the class declaring the wrapped field. + */ +public final class ReflectField extends ReflectMember { + + 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 c, String name, boolean bypassFilter) throws NoSuchFieldException { + super(c, name, bypassFilter); + } + + @Override + /* package */ Field fetchFromClass(Class 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); + } + +} diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMember.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMember.java new file mode 100644 index 0000000..bccdefb --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMember.java @@ -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 the type of the class declaring the wrapped member. + * @param type of object uniquely identifying the member into the class. + * @param the type of the class member (like {@link Field}, {@link Method} or {@link Constructor}) + * @param 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 + permits ReflectField, ReflectMethod, ReflectConstructor { + + /* package */ final ReflectClass reflectClass; + /* package */ final ID identifier; + private final EL member; + + /* package */ ReflectMember(ReflectClass 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 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 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(); + } + +} diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMethod.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMethod.java new file mode 100644 index 0000000..37ee479 --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/ReflectMethod.java @@ -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 the type of the class declaring the wrapped method. + */ +public final class ReflectMethod extends ReflectMember { + + /* package */ ReflectMethod(ReflectClass c, MethodIdentifier methodId, boolean bypassFilter) throws NoSuchMethodException { + super(c, methodId, bypassFilter); + } + + @Override + protected Method fetchFromClass(Class 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); + } + +}