Add basic reflection support
This commit is contained in:
parent
dd7bc714b9
commit
97a0218bf6
14 changed files with 1667 additions and 2 deletions
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.exceptions;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Thrown when a sequence of if checks or switch cases fail unexpectedly.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public class UnexpectedCheckEndException extends RuntimeException {
|
||||
/**
|
||||
* Constructs this exception.
|
||||
*
|
||||
* @param checkOccurrence the sequence of checks that failed
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public UnexpectedCheckEndException(@NotNull String checkOccurrence) {
|
||||
super("A sequence of if checks or switch cases failed unexpectedly while " + checkOccurrence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs this exception.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public UnexpectedCheckEndException() {
|
||||
super("A sequence of if checks or switch cases failed unexpectedly");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.exceptions.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.types.reflection.ClassType;
|
||||
import de.staropensource.sosengine.base.utility.ListFormatter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Thrown when the method called does not apply to the class type.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public class IncompatibleTypeReflection extends RuntimeException {
|
||||
/**
|
||||
* Constructs this exception.
|
||||
*
|
||||
* @param methodName name of the method that failed
|
||||
* @param requiredClassType class type received by the method
|
||||
* @param compatibleTypes class types the method is compatible with
|
||||
*/
|
||||
public IncompatibleTypeReflection(@NotNull String methodName, @NotNull ClassType requiredClassType, @NotNull ClassType[] compatibleTypes) {
|
||||
super("The method ReflectionClass#" + methodName + " only applies to type(s) " + ListFormatter.formatArray(compatibleTypes) + ", not " + requiredClassType.name());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.exceptions.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.reflection.ReflectionClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Thrown when a field could not be found.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public class InvalidFieldException extends Exception {
|
||||
/**
|
||||
* Constructs this exception.
|
||||
*
|
||||
* @param clazz caller {@link ReflectionClass}
|
||||
* @param fieldName name of the invalid field
|
||||
*/
|
||||
public InvalidFieldException(@NotNull ReflectionClass clazz, @NotNull String fieldName) {
|
||||
super("Invalid field name \"" + fieldName + "\" in class " + clazz.getPath());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.exceptions.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.reflection.ReflectionClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Thrown when a method could not be found.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public class InvalidMethodException extends Exception {
|
||||
/**
|
||||
* Constructs this exception.
|
||||
*
|
||||
* @param clazz caller {@link ReflectionClass}
|
||||
* @param fieldName name of the invalid method
|
||||
*/
|
||||
public InvalidMethodException(@NotNull ReflectionClass clazz, @NotNull String fieldName) {
|
||||
super("Invalid method name \"" + fieldName + "\" in class " + clazz.getPath());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.exceptions.reflection;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Thrown if access to some class, method or field is denied.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public class NoAccessException extends Exception {
|
||||
public NoAccessException(@NotNull String type, @NotNull String name) {
|
||||
super("Access to " + type + " " + name + " has been denied");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.internal.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.exceptions.UnexpectedThrowableException;
|
||||
import de.staropensource.sosengine.base.exceptions.reflection.NoAccessException;
|
||||
import de.staropensource.sosengine.base.reflection.ReflectionField;
|
||||
import de.staropensource.sosengine.base.reflection.ReflectionMethod;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
|
||||
/**
|
||||
* Allows access to fields and methods to be widened.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public class ReflectionAccessWidener {
|
||||
/**
|
||||
* Allows access to an {@link AccessibleObject}.
|
||||
* <p>
|
||||
* Stolen from <a href="https://github.com/jOOQ/jOOR">the jOOR library</a>.
|
||||
* All credits to them.
|
||||
*
|
||||
* @param accessible object to allow access to
|
||||
*/
|
||||
public static <T extends AccessibleObject> void allowAccess(T accessible) {
|
||||
if (accessible == null)
|
||||
return;
|
||||
|
||||
if (accessible instanceof Member member)
|
||||
if (Modifier.isPublic(member.getModifiers()) && Modifier.isPublic(member.getDeclaringClass().getModifiers()))
|
||||
return;
|
||||
|
||||
//noinspection deprecation // no, it's what we want
|
||||
if (!accessible.isAccessible())
|
||||
accessible.setAccessible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks modifications to some field.
|
||||
*
|
||||
* @param reflectionField {@link ReflectionField} to unlock
|
||||
* @return updated modifiers. pass those to {@code lockModifications} after modifying the field
|
||||
* @see ReflectionAccessWidener#lockModifications(ReflectionField, int)
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public static int unlockModifications(ReflectionField reflectionField) throws UnexpectedThrowableException, NoAccessException {
|
||||
int updatedModifiers = 0;
|
||||
Field field = reflectionField.getField();
|
||||
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = field.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside field " + field.getName());
|
||||
}
|
||||
|
||||
modifiersField.setAccessible(true);
|
||||
|
||||
if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL)
|
||||
updatedModifiers = ~Modifier.FINAL;
|
||||
if ((field.getModifiers() & Modifier.STATIC) == Modifier.STATIC)
|
||||
updatedModifiers = updatedModifiers & ~Modifier.STATIC;
|
||||
|
||||
try {
|
||||
modifiersField.setInt(field, field.getModifiers() & ~updatedModifiers);
|
||||
} catch (IllegalAccessException exception) {
|
||||
throw new NoAccessException("field", field.getName());
|
||||
}
|
||||
|
||||
return updatedModifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks modifications to some method.
|
||||
*
|
||||
* @param reflectionMethod {@link ReflectionMethod} to unlock
|
||||
* @return updated modifiers. pass those to {@code lockModifications} after modifying the method
|
||||
* @see ReflectionAccessWidener#lockModifications(ReflectionMethod, int)
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public static int unlockModifications(ReflectionMethod reflectionMethod) throws UnexpectedThrowableException, NoAccessException {
|
||||
Method method = reflectionMethod.getMethod();
|
||||
int updatedModifiers = method.getModifiers();
|
||||
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = method.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside method " + method.getName());
|
||||
}
|
||||
|
||||
modifiersField.setAccessible(true);
|
||||
|
||||
if (Modifier.isFinal(method.getModifiers()))
|
||||
updatedModifiers = ~Modifier.FINAL;
|
||||
if (Modifier.isStatic(method.getModifiers()))
|
||||
updatedModifiers = updatedModifiers & ~Modifier.STATIC;
|
||||
if (!Modifier.isPublic(method.getModifiers())) {
|
||||
if (Modifier.isProtected(method.getModifiers()))
|
||||
updatedModifiers = updatedModifiers & ~Modifier.PROTECTED;
|
||||
if (Modifier.isPrivate(method.getModifiers()))
|
||||
updatedModifiers = updatedModifiers & ~Modifier.PRIVATE;
|
||||
|
||||
updatedModifiers = updatedModifiers & ~Modifier.PUBLIC;
|
||||
}
|
||||
try {
|
||||
modifiersField.setInt(method, method.getModifiers() & ~updatedModifiers);
|
||||
} catch (IllegalAccessException exception) {
|
||||
throw new NoAccessException("method", method.getName());
|
||||
}
|
||||
|
||||
return updatedModifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks modifications to some field.
|
||||
*
|
||||
* @param reflectionField {@link ReflectionField} to lock
|
||||
* @param updatedModifiers original modifiers
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is not present inside the field
|
||||
* @see ReflectionAccessWidener#unlockModifications(ReflectionField)
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public static void lockModifications(ReflectionField reflectionField, int updatedModifiers) throws UnexpectedThrowableException, NoAccessException {
|
||||
Field field = reflectionField.getField();
|
||||
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = field.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside field " + field.getName());
|
||||
}
|
||||
|
||||
modifiersField.setAccessible(true);
|
||||
|
||||
try {
|
||||
modifiersField.setInt(field, field.getModifiers() & ~updatedModifiers);
|
||||
} catch (IllegalAccessException exception) {
|
||||
throw new NoAccessException("field", field.getName());
|
||||
}
|
||||
|
||||
modifiersField.setAccessible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks modifications to some method.
|
||||
*
|
||||
* @param reflectionMethod {@link ReflectionMethod} to lock
|
||||
* @param updatedModifiers original modifiers
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is not present inside the method
|
||||
* @see ReflectionAccessWidener#unlockModifications(ReflectionMethod)
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public static void lockModifications(ReflectionMethod reflectionMethod, int updatedModifiers) throws UnexpectedThrowableException, NoAccessException {
|
||||
Method method = reflectionMethod.getMethod();
|
||||
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = method.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside method " + method.getName());
|
||||
}
|
||||
|
||||
modifiersField.setAccessible(true);
|
||||
|
||||
try {
|
||||
modifiersField.setInt(method, method.getModifiers() & ~updatedModifiers);
|
||||
} catch (IllegalAccessException exception) {
|
||||
throw new NoAccessException("method", method.getName());
|
||||
}
|
||||
|
||||
modifiersField.setAccessible(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.internal.reflection;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helps in scanning the class path.
|
||||
* <p>
|
||||
* This entire class has been stolen from
|
||||
* <a href="https://github.com/ronmamo/reflections">the Reflections library</a>.
|
||||
* All credits to them.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public class ReflectionScanningHelper {
|
||||
/**
|
||||
* Returns the classpath as URLs.
|
||||
*
|
||||
* @return collection of classpath urls
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@NotNull
|
||||
public static Collection<URL> getClasspathURLs() {
|
||||
Collection<URL> urls = new ArrayList<>();
|
||||
String javaClassPath = System.getProperty("java.class.path");
|
||||
if (javaClassPath != null) {
|
||||
for (String path : javaClassPath.split(File.pathSeparator)) {
|
||||
try {
|
||||
urls.add(new File(path).toURI().toURL());
|
||||
} catch (Exception e) {
|
||||
if (Reflections.log != null) {
|
||||
Reflections.log.warn("Could not get URL", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fixURLs(urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes slowdowns which {@link URL}s may cause.
|
||||
* <p>
|
||||
* Visit <a href="http://michaelscharf.blogspot.co.il/2006/11/javaneturlequals-and-hashcode-make.html">this blog post</a> for more information.
|
||||
*
|
||||
* @param urls unfixed urls
|
||||
* @return fixed urls
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@NotNull
|
||||
public static Collection<URL> fixURLs(@NotNull Collection<URL> urls) {
|
||||
Map<String, URL> distinct = new LinkedHashMap<>(urls.size());
|
||||
for (URL url : urls) {
|
||||
distinct.put(url.toExternalForm(), url);
|
||||
}
|
||||
return distinct.values();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.reflection;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* The class you'd likely want to use for reflection.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public final class Reflect {
|
||||
/**
|
||||
* Allows reflecting on some class.
|
||||
*
|
||||
* @param clazz class to reflect on
|
||||
* @return new {@link ReflectionClass}
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@NotNull
|
||||
public static ReflectionClass reflectOn(@NotNull Class<?> clazz) {
|
||||
return new ReflectionClass(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows reflecting on some method.
|
||||
*
|
||||
* @param method method to reflect on
|
||||
* @return new {@link ReflectionMethod}
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@NotNull
|
||||
public static ReflectionMethod reflectOn(@NotNull Method method) {
|
||||
return new ReflectionMethod(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows reflecting on some field.
|
||||
*
|
||||
* @param field field to reflect on
|
||||
* @return new {@link ReflectionField}
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@NotNull
|
||||
public static ReflectionField reflectOn(@NotNull Field field) {
|
||||
return new ReflectionField(field);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.exceptions.UnexpectedCheckEndException;
|
||||
import de.staropensource.sosengine.base.exceptions.reflection.IncompatibleTypeReflection;
|
||||
import de.staropensource.sosengine.base.exceptions.reflection.InvalidFieldException;
|
||||
import de.staropensource.sosengine.base.exceptions.reflection.InvalidMethodException;
|
||||
import de.staropensource.sosengine.base.types.reflection.ClassType;
|
||||
import de.staropensource.sosengine.base.types.reflection.VisibilityModifier;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Allows reflection on classes.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused", "JavadocDeclaration", "JavadocBlankLines" })
|
||||
@Getter
|
||||
public final class ReflectionClass {
|
||||
/**
|
||||
* The class to reflect on.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*
|
||||
* -- GETTER --
|
||||
* Returns the class to reflect on.
|
||||
*
|
||||
* @return class reference
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
private final Class<?> clazz;
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
* @param clazz class to reflect on
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public ReflectionClass(@NotNull Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class name.
|
||||
*
|
||||
* @return class name
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public String getName() {
|
||||
return clazz.getName().replace(getPackage() + ".", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package path.
|
||||
*
|
||||
* @return package path
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public String getPackage() {
|
||||
return clazz.getPackage().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path, including the package and class name.
|
||||
*
|
||||
* @return full path
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public String getPath() {
|
||||
return clazz.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this class.
|
||||
*
|
||||
* @return class type
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public ClassType getType() {
|
||||
if (clazz.isLocalClass() || clazz.isMemberClass() || clazz.isAnonymousClass())
|
||||
return ClassType.CLASS;
|
||||
else if (clazz.isInterface())
|
||||
return ClassType.INTERFACE;
|
||||
else if (clazz.isEnum())
|
||||
return ClassType.ENUM;
|
||||
else if (clazz.isRecord())
|
||||
return ClassType.RECORD;
|
||||
else {
|
||||
// Class#isUnnamedClass is in preview, use reflection to invoke method
|
||||
try {
|
||||
if ((boolean) clazz.getMethod("isUnnamedClass").invoke(clazz))
|
||||
return ClassType.CLASS;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return ClassType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class' visibility.
|
||||
*
|
||||
* @return class visibility
|
||||
* @throws UnexpectedCheckEndException if the class is neither public, protected or private (should be impossible)
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public VisibilityModifier getVisibility() throws UnexpectedCheckEndException {
|
||||
if (Modifier.isPublic(clazz.getModifiers()))
|
||||
return VisibilityModifier.PUBLIC;
|
||||
else if (Modifier.isProtected(clazz.getModifiers()))
|
||||
return VisibilityModifier.PROTECTED;
|
||||
else if (Modifier.isPrivate(clazz.getModifiers()))
|
||||
return VisibilityModifier.PRIVATE;
|
||||
else
|
||||
throw new UnexpectedCheckEndException("checking the visibility of a class with modifiers " + clazz.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code final} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code final} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isFinal() throws IncompatibleTypeReflection {
|
||||
if (getType() != ClassType.CLASS)
|
||||
throw new IncompatibleTypeReflection("isFinal", getType(), new ClassType[]{ ClassType.CLASS });
|
||||
return Modifier.isFinal(clazz.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code abstract} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code abstract} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isAbstract() throws IncompatibleTypeReflection {
|
||||
if (getType() != ClassType.CLASS)
|
||||
throw new IncompatibleTypeReflection("isAbstract", getType(), new ClassType[]{ClassType.CLASS});
|
||||
return Modifier.isAbstract(clazz.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all annotations this class has.
|
||||
*
|
||||
* @return array of all annotations
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Annotation[] getAnnotations() {
|
||||
return clazz.getAnnotations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified annotation or {@code null} if not found.
|
||||
*
|
||||
* @param annotation class
|
||||
* @return annotation or {@code null}
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Annotation getAnnotation(@NotNull Class<Annotation> annotation) {
|
||||
return clazz.getAnnotation(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified method.
|
||||
*
|
||||
* @param methodName name of the method
|
||||
* @return new {@link ReflectionMethod} instance
|
||||
* @throws InvalidMethodException if the method does not exist
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public ReflectionMethod getMethod(@NotNull String methodName, Class<?>... methodArguments) throws InvalidMethodException {
|
||||
try {
|
||||
return new ReflectionMethod(clazz.getDeclaredMethod(methodName, methodArguments));
|
||||
} catch (NoSuchMethodException exception) {
|
||||
throw new InvalidMethodException(this, methodName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified field.
|
||||
*
|
||||
* @param fieldName name of the field
|
||||
* @return new {@link ReflectionField} instance
|
||||
* @throws InvalidFieldException if the field does not exist
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public ReflectionField getField(@NotNull String fieldName) throws InvalidFieldException {
|
||||
try {
|
||||
return new ReflectionField(clazz.getField(fieldName));
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new InvalidFieldException(this, fieldName);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.exceptions.UnexpectedCheckEndException;
|
||||
import de.staropensource.sosengine.base.exceptions.UnexpectedThrowableException;
|
||||
import de.staropensource.sosengine.base.exceptions.reflection.NoAccessException;
|
||||
import de.staropensource.sosengine.base.internal.reflection.ReflectionAccessWidener;
|
||||
import de.staropensource.sosengine.base.types.reflection.VisibilityModifier;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Allows reflection on methods.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused", "JavadocDeclaration", "JavadocBlankLines" })
|
||||
@Getter
|
||||
public final class ReflectionField {
|
||||
|
||||
/**
|
||||
* The class the method is contained in.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*
|
||||
* -- GETTER --
|
||||
* Returns the class the method is contained in.
|
||||
*
|
||||
* @return parent class
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
private final Class<?> parentClass;
|
||||
|
||||
/**
|
||||
* The field to reflect on.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*
|
||||
* -- GETTER --
|
||||
* Returns the field to reflect on.
|
||||
*
|
||||
* @return field reference
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
private final Field field;
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
* @param field field to reflect on
|
||||
*/
|
||||
public ReflectionField(@NotNull Field field) {
|
||||
parentClass = null;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
* @param parentClass clazz this field is contained in
|
||||
* @param field field to reflect on
|
||||
*/
|
||||
public ReflectionField(@NotNull Class<?> parentClass, @NotNull Field field) {
|
||||
this.parentClass = parentClass;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the field.
|
||||
*
|
||||
* @return field name
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public String getName() {
|
||||
return field.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field's visibility.
|
||||
*
|
||||
* @return field visibility
|
||||
* @throws UnexpectedCheckEndException if the field is neither public, protected or private (should be impossible)
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public VisibilityModifier getVisibility() throws UnexpectedCheckEndException {
|
||||
if (Modifier.isPublic(field.getModifiers()))
|
||||
return VisibilityModifier.PUBLIC;
|
||||
else if (Modifier.isProtected(field.getModifiers()))
|
||||
return VisibilityModifier.PROTECTED;
|
||||
else if (Modifier.isPrivate(field.getModifiers()))
|
||||
return VisibilityModifier.PRIVATE;
|
||||
else
|
||||
throw new UnexpectedCheckEndException("checking the visibility of a field with modifiers " + field.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code final} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code final} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isFinal() {
|
||||
return Modifier.isFinal(field.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code static} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code static} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(field.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code transient} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code transient} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isTransient() {
|
||||
return Modifier.isTransient(field.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code volatile} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code volatile} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isVolatile() {
|
||||
return Modifier.isVolatile(field.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code final} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code final} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setFinal(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isFinal() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = field.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside field " + field.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(field, modifiersField.getInt(field) & ~Modifier.FINAL);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code static} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code static} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setStatic(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isStatic() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = field.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside field " + field.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(field, modifiersField.getInt(field) & ~Modifier.STATIC);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code transient} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code transient} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setTransient(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isTransient() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = field.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside field " + field.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(field, modifiersField.getInt(field) & ~Modifier.TRANSIENT);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code volatile} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code volatile} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setVolatile(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isVolatile() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = field.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside field " + field.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(field, modifiersField.getInt(field) & ~Modifier.VOLATILE);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all annotations this field has.
|
||||
*
|
||||
* @return array of all annotations
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Annotation[] getAnnotations() {
|
||||
return field.getAnnotations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified annotation or {@code null} if not found.
|
||||
*
|
||||
* @param annotation class
|
||||
* @return annotation or {@code null}
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Annotation getAnnotation(@NotNull Class<Annotation> annotation) {
|
||||
return field.getAnnotation(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type this field has.
|
||||
*
|
||||
* @return field type
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Object getType() {
|
||||
return field.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the generic type this field has.
|
||||
* <p>
|
||||
* <a href="https://docs.oracle.com/javase/tutorial/reflect/member/fieldTypes.html">You can read the between {@code getType} and {@code getGenericType} here.</a>
|
||||
*
|
||||
* @return field type
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Object getGenericType() {
|
||||
return field.getGenericType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the field with a new value.
|
||||
*
|
||||
* @param newValue new value
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setValue(Object newValue) throws NoAccessException {
|
||||
try {
|
||||
field.set(parentClass, newValue);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the field with a new value.
|
||||
*
|
||||
* @return field's value
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Object getValue() throws NoAccessException {
|
||||
try {
|
||||
return field.get(parentClass);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", getName());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.exceptions.UnexpectedCheckEndException;
|
||||
import de.staropensource.sosengine.base.exceptions.UnexpectedThrowableException;
|
||||
import de.staropensource.sosengine.base.exceptions.reflection.NoAccessException;
|
||||
import de.staropensource.sosengine.base.internal.reflection.ReflectionAccessWidener;
|
||||
import de.staropensource.sosengine.base.types.reflection.VisibilityModifier;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Allows reflection on methods.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused", "JavadocDeclaration", "JavadocBlankLines" })
|
||||
@Getter
|
||||
public final class ReflectionMethod {
|
||||
/**
|
||||
* The class the method is contained in.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*
|
||||
* -- GETTER --
|
||||
* Returns the class the method is contained in.
|
||||
*
|
||||
* @return parent class
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
private final Class<?> parentClass;
|
||||
|
||||
/**
|
||||
* The method to reflect on.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*
|
||||
* -- GETTER --
|
||||
* Returns the method to reflect on.
|
||||
*
|
||||
* @return method reference
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
private final Method method;
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
* @param method method to reflect on
|
||||
*/
|
||||
public ReflectionMethod(@NotNull Method method) {
|
||||
parentClass = null;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs this class.
|
||||
*
|
||||
* @param parentClass clazz this method is contained in
|
||||
* @param method method to reflect on
|
||||
*/
|
||||
public ReflectionMethod(@NotNull Class<?> parentClass, @NotNull Method method) {
|
||||
this.parentClass = parentClass;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the method.
|
||||
*
|
||||
* @return method name
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method's visibility.
|
||||
*
|
||||
* @return method visibility
|
||||
* @throws UnexpectedCheckEndException if the method is neither public, protected or private (should be impossible)
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public VisibilityModifier getVisibility() throws UnexpectedCheckEndException {
|
||||
if (Modifier.isPublic(method.getModifiers()))
|
||||
return VisibilityModifier.PUBLIC;
|
||||
else if (Modifier.isProtected(method.getModifiers()))
|
||||
return VisibilityModifier.PROTECTED;
|
||||
else if (Modifier.isPrivate(method.getModifiers()))
|
||||
return VisibilityModifier.PRIVATE;
|
||||
else
|
||||
throw new UnexpectedCheckEndException("checking the visibility of a method with modifiers " + method.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code final} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code final} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isFinal() {
|
||||
return Modifier.isFinal(method.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code static} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code static} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(method.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code abstract} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code abstract} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isAbstract() {
|
||||
return Modifier.isAbstract(method.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the {@code synchronized} modifier is present.
|
||||
*
|
||||
* @return presence of the {@code synchronized} modifier
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public boolean isSynchronized() {
|
||||
return Modifier.isSynchronized(method.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code final} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code final} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setFinal(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isFinal() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = method.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside method " + method.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(method, modifiersField.getInt(method) & ~Modifier.FINAL);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code static} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code static} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setStatic(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isStatic() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = method.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside method " + method.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(method, modifiersField.getInt(method) & ~Modifier.STATIC);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code abstract} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code abstract} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setAbstract(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isAbstract() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = method.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside method " + method.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(method, modifiersField.getInt(method) & ~Modifier.ABSTRACT);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the presence of the {@code synchronized} modifier.
|
||||
*
|
||||
* @param newValue new presence of the {@code synchronized} modifier
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field is missing
|
||||
* @throws NoAccessException if access to the {@code modifiers} field has been denied
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public void setSynchronized(boolean newValue) throws UnexpectedThrowableException, NoAccessException {
|
||||
// Don't do anything if the new value already matches the current value
|
||||
if (isSynchronized() == newValue)
|
||||
return;
|
||||
|
||||
// Unlock modifications
|
||||
int modifiedModifiers = ReflectionAccessWidener.unlockModifications(this);
|
||||
|
||||
// Get 'modifiers' field
|
||||
Field modifiersField;
|
||||
try {
|
||||
modifiersField = method.getClass().getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new UnexpectedThrowableException(exception, "Field \"modifiers\" not present inside method " + method.getName());
|
||||
}
|
||||
|
||||
// Update 'modifiers' field
|
||||
try {
|
||||
modifiersField.setInt(method, modifiersField.getInt(method) & ~Modifier.SYNCHRONIZED);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new NoAccessException("field", "modifiers");
|
||||
}
|
||||
|
||||
// Lock modifications
|
||||
ReflectionAccessWidener.lockModifications(this, modifiedModifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all annotations this method has.
|
||||
*
|
||||
* @return array of all annotations
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Annotation[] getAnnotations() {
|
||||
return method.getAnnotations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the specified annotation or {@code null} if not found.
|
||||
*
|
||||
* @param annotation class
|
||||
* @return annotation or {@code null}
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Annotation getAnnotation(@NotNull Class<Annotation> annotation) {
|
||||
return method.getAnnotation(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method's return type.
|
||||
*
|
||||
* @return method return type
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@NotNull
|
||||
public Class<?> getReturnType() {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method's generic return type.
|
||||
* <p>
|
||||
* <a href="https://docs.oracle.com/javase/tutorial/reflect/member/fieldTypes.html">You can read the between {@code getReturnType} and {@code getGenericReturnType} here.</a>
|
||||
*
|
||||
* @return field type
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public Object getGenericReturnType() {
|
||||
return method.getGenericReturnType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method.
|
||||
*
|
||||
* @return method return value
|
||||
* @throws NoAccessException if access to the method has been denied
|
||||
* @throws InvocationTargetException covers exceptions thrown by the method
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field could not be found
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@Nullable
|
||||
public Object invoke() throws NoAccessException, InvocationTargetException, UnexpectedThrowableException {
|
||||
return invoke(new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method.
|
||||
*
|
||||
* @return method return value
|
||||
* @throws NoAccessException if access to the method has been denied
|
||||
* @throws InvocationTargetException covers exceptions thrown by the method
|
||||
* @throws UnexpectedThrowableException if the {@code modifiers} field could not be found
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@Nullable
|
||||
public Object invoke(Object... args) throws NoAccessException, InvocationTargetException, UnexpectedThrowableException {
|
||||
Object returnValue;
|
||||
|
||||
// Allow access to method
|
||||
ReflectionAccessWidener.allowAccess(method);
|
||||
|
||||
// Invoke method
|
||||
try {
|
||||
returnValue = method.invoke(parentClass, args);
|
||||
} catch (IllegalAccessException exception) {
|
||||
//ReflectionAccessWidener.lockModifications(this, updatedModifiers); // Lock method before throwing exception
|
||||
throw new NoAccessException("method", getName());
|
||||
}
|
||||
|
||||
// Return return value from method
|
||||
return returnValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.types.reflection;
|
||||
|
||||
/**
|
||||
* Identifies a class' type.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public enum ClassType {
|
||||
/**
|
||||
* Identifies the class as a {@code class} class.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
CLASS,
|
||||
|
||||
/**
|
||||
* Identifies the class as an {@code interface} class.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
INTERFACE,
|
||||
|
||||
/**
|
||||
* Identifies the class as an {@code enum} class.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
ENUM,
|
||||
|
||||
/**
|
||||
* Identifies the class as a {@code record} class.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
RECORD,
|
||||
|
||||
/**
|
||||
* Identifies the class as some unknown class the engine does not yet know of.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
UNKNOWN,
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* STAROPENSOURCE ENGINE SOURCE FILE
|
||||
* Copyright (c) 2024 The StarOpenSource Engine Contributors
|
||||
* Licensed under the GNU Affero General Public License v3
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.staropensource.sosengine.base.types.reflection;
|
||||
|
||||
import de.staropensource.sosengine.base.exceptions.UnexpectedCheckEndException;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Determines which visibility a class, method or field has.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
@SuppressWarnings({ "unused" })
|
||||
public enum VisibilityModifier {
|
||||
/**
|
||||
* Marks the class, method or field as public.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
PUBLIC,
|
||||
|
||||
/**
|
||||
* Marks the class, method or field as protected.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
PROTECTED,
|
||||
|
||||
/**
|
||||
* Marks the class, method or field as private.
|
||||
*
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
PRIVATE;
|
||||
|
||||
/**
|
||||
* Converts the visibility into a usable modifier bit.
|
||||
*
|
||||
* @return int
|
||||
* @since v1-alpha2
|
||||
*/
|
||||
public int getModifier() {
|
||||
switch (this) {
|
||||
case PUBLIC -> {
|
||||
return Modifier.PUBLIC;
|
||||
}
|
||||
case PROTECTED -> {
|
||||
return Modifier.PROTECTED;
|
||||
}
|
||||
case PRIVATE -> {
|
||||
return Modifier.PRIVATE;
|
||||
}
|
||||
case null, default -> {
|
||||
throw new UnexpectedCheckEndException("converting the visibility into a modifier");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,15 +16,18 @@ module sosengine.base {
|
|||
exports de.staropensource.sosengine.base.annotations;
|
||||
exports de.staropensource.sosengine.base.classes;
|
||||
exports de.staropensource.sosengine.base.classes.helpers;
|
||||
exports de.staropensource.sosengine.base.types.logging;
|
||||
exports de.staropensource.sosengine.base.data.info;
|
||||
exports de.staropensource.sosengine.base.data.versioning;
|
||||
exports de.staropensource.sosengine.base.events;
|
||||
exports de.staropensource.sosengine.base.exceptions;
|
||||
exports de.staropensource.sosengine.base.exceptions.reflection;
|
||||
exports de.staropensource.sosengine.base.logging;
|
||||
exports de.staropensource.sosengine.base.logging.implementation;
|
||||
exports de.staropensource.sosengine.base.reflection;
|
||||
exports de.staropensource.sosengine.base.types;
|
||||
exports de.staropensource.sosengine.base.types.immutable;
|
||||
exports de.staropensource.sosengine.base.types.logging;
|
||||
exports de.staropensource.sosengine.base.types.reflection;
|
||||
exports de.staropensource.sosengine.base.types.vectors;
|
||||
exports de.staropensource.sosengine.base.utility;
|
||||
exports de.staropensource.sosengine.base.utility.converter;
|
||||
|
@ -39,15 +42,18 @@ module sosengine.base {
|
|||
opens de.staropensource.sosengine.base.annotations;
|
||||
opens de.staropensource.sosengine.base.classes;
|
||||
opens de.staropensource.sosengine.base.classes.helpers;
|
||||
opens de.staropensource.sosengine.base.types.logging;
|
||||
opens de.staropensource.sosengine.base.data.info;
|
||||
opens de.staropensource.sosengine.base.data.versioning;
|
||||
opens de.staropensource.sosengine.base.events;
|
||||
opens de.staropensource.sosengine.base.exceptions;
|
||||
opens de.staropensource.sosengine.base.exceptions.reflection;
|
||||
opens de.staropensource.sosengine.base.logging;
|
||||
opens de.staropensource.sosengine.base.logging.implementation;
|
||||
opens de.staropensource.sosengine.base.reflection;
|
||||
opens de.staropensource.sosengine.base.types;
|
||||
opens de.staropensource.sosengine.base.types.immutable;
|
||||
opens de.staropensource.sosengine.base.types.logging;
|
||||
opens de.staropensource.sosengine.base.types.reflection;
|
||||
opens de.staropensource.sosengine.base.types.vectors;
|
||||
opens de.staropensource.sosengine.base.utility;
|
||||
opens de.staropensource.sosengine.base.utility.converter;
|
||||
|
|
Loading…
Reference in a new issue