From 97a0218bf623966db3d72891b371acd866b10653 Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Mon, 15 Jul 2024 13:13:35 +0200 Subject: [PATCH] Add basic reflection support --- .../UnexpectedCheckEndException.java | 48 +++ .../IncompatibleTypeReflection.java | 43 ++ .../reflection/InvalidFieldException.java | 41 ++ .../reflection/InvalidMethodException.java | 41 ++ .../reflection/NoAccessException.java | 34 ++ .../reflection/ReflectionAccessWidener.java | 193 +++++++++ .../reflection/ReflectionScanningHelper.java | 84 ++++ .../sosengine/base/reflection/Reflect.java | 69 ++++ .../base/reflection/ReflectionClass.java | 214 ++++++++++ .../base/reflection/ReflectionField.java | 368 +++++++++++++++++ .../base/reflection/ReflectionMethod.java | 385 ++++++++++++++++++ .../base/types/reflection/ClassType.java | 63 +++ .../types/reflection/VisibilityModifier.java | 76 ++++ base/src/main/java/module-info.java | 10 +- 14 files changed, 1667 insertions(+), 2 deletions(-) create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/UnexpectedCheckEndException.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/IncompatibleTypeReflection.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidFieldException.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodException.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/NoAccessException.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionAccessWidener.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionScanningHelper.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/reflection/Reflect.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionClass.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionField.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/types/reflection/ClassType.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/types/reflection/VisibilityModifier.java diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/UnexpectedCheckEndException.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/UnexpectedCheckEndException.java new file mode 100644 index 00000000..1a51e972 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/UnexpectedCheckEndException.java @@ -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 . + */ + +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"); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/IncompatibleTypeReflection.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/IncompatibleTypeReflection.java new file mode 100644 index 00000000..52e18408 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/IncompatibleTypeReflection.java @@ -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 . + */ + +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()); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidFieldException.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidFieldException.java new file mode 100644 index 00000000..ac6c4fbc --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidFieldException.java @@ -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 . + */ + +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()); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodException.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodException.java new file mode 100644 index 00000000..e30620be --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/InvalidMethodException.java @@ -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 . + */ + +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()); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/NoAccessException.java b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/NoAccessException.java new file mode 100644 index 00000000..5277c652 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/exceptions/reflection/NoAccessException.java @@ -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 . + */ + +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"); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionAccessWidener.java b/base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionAccessWidener.java new file mode 100644 index 00000000..87cd50cd --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionAccessWidener.java @@ -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 . + */ + +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}. + *

+ * Stolen from the jOOR library. + * All credits to them. + * + * @param accessible object to allow access to + */ + public static 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); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionScanningHelper.java b/base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionScanningHelper.java new file mode 100644 index 00000000..ca90b2c8 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/internal/reflection/ReflectionScanningHelper.java @@ -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 . + */ + +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. + *

+ * This entire class has been stolen from + * the Reflections library. + * 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 getClasspathURLs() { + Collection 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. + *

+ * Visit this blog post for more information. + * + * @param urls unfixed urls + * @return fixed urls + * @since v1-alpha2 + */ + @NotNull + public static Collection fixURLs(@NotNull Collection urls) { + Map distinct = new LinkedHashMap<>(urls.size()); + for (URL url : urls) { + distinct.put(url.toExternalForm(), url); + } + return distinct.values(); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/reflection/Reflect.java b/base/src/main/java/de/staropensource/sosengine/base/reflection/Reflect.java new file mode 100644 index 00000000..f43f7a3b --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/reflection/Reflect.java @@ -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 . + */ + +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); + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionClass.java b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionClass.java new file mode 100644 index 00000000..e23268c9 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionClass.java @@ -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 . + */ + +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) { + 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); + } + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionField.java b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionField.java new file mode 100644 index 00000000..2978aaed --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionField.java @@ -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 . + */ + +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) { + 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. + *

+ * You can read the between {@code getType} and {@code getGenericType} here. + * + * @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()); + } + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java new file mode 100644 index 00000000..83ac2ea9 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/reflection/ReflectionMethod.java @@ -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 . + */ + +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) { + 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. + *

+ * You can read the between {@code getReturnType} and {@code getGenericReturnType} here. + * + * @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; + } +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/types/reflection/ClassType.java b/base/src/main/java/de/staropensource/sosengine/base/types/reflection/ClassType.java new file mode 100644 index 00000000..264cdc81 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/types/reflection/ClassType.java @@ -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 . + */ + +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, +} diff --git a/base/src/main/java/de/staropensource/sosengine/base/types/reflection/VisibilityModifier.java b/base/src/main/java/de/staropensource/sosengine/base/types/reflection/VisibilityModifier.java new file mode 100644 index 00000000..603d853c --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/types/reflection/VisibilityModifier.java @@ -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 . + */ + +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"); + } + } + } +} diff --git a/base/src/main/java/module-info.java b/base/src/main/java/module-info.java index fd2b0736..5de2afa2 100644 --- a/base/src/main/java/module-info.java +++ b/base/src/main/java/module-info.java @@ -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;