From 6111dece6178fa233c26a34b5b350c3a23df3d4b Mon Sep 17 00:00:00 2001 From: altrisi Date: Thu, 17 Mar 2022 21:21:44 +0100 Subject: [PATCH] Ease concrete restrictions on Annotated Scarpet API and make instantiating lazy Prevents instantiating classes with all annotated methods static, making it also more flexible in that you can put annotated functions near to other API that may be initialized --- .../script/annotation/AnnotationParser.java | 52 ++++++++++--------- .../script/annotation/ScarpetFunction.java | 7 ++- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/main/java/carpet/script/annotation/AnnotationParser.java b/src/main/java/carpet/script/annotation/AnnotationParser.java index 711a4bb2..891c64a3 100644 --- a/src/main/java/carpet/script/annotation/AnnotationParser.java +++ b/src/main/java/carpet/script/annotation/AnnotationParser.java @@ -12,11 +12,14 @@ import java.util.List; import java.util.ListIterator; import java.util.Objects; import java.util.Optional; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; +import com.google.common.base.Suppliers; + import carpet.CarpetExtension; import carpet.script.Context; import carpet.script.Expression; @@ -26,6 +29,7 @@ import carpet.script.Fluff.UsageProvider; import carpet.script.exception.InternalExpressionException; import carpet.script.LazyValue; import carpet.script.value.Value; +import net.fabricmc.api.ModInitializer; /** *

This class parses methods annotated with the {@link ScarpetFunction} annotation in a given {@link Class}, generating @@ -81,13 +85,11 @@ public final class AnnotationParser *

Parses a given {@link Class} and registers its annotated methods, the ones with the {@link ScarpetFunction} annotation, * to be used in the Scarpet language.

* - *

Only call this method once per class per lifetime of the JVM! (for example, at {@link CarpetExtension#onGameStarted()})

+ *

Only call this method once per class per lifetime of the JVM! (for example, at {@link CarpetExtension#onGameStarted()} or + * {@link ModInitializer#onInitialize()}).

* *

There is a set of requirements for the class and its methods:

* + *

Additionally, if the class contains annotated instance (non-static) methods, the class must be concrete and provide a no-arg constructor + * to instantiate it.

* * @see ScarpetFunction - * @param The generic type of the class to parse. * @param clazz The class to parse */ - public static void parseFunctionClass(Class clazz) + public static void parseFunctionClass(Class clazz) { - if (Modifier.isAbstract(clazz.getModifiers())) - throw new IllegalArgumentException("Function class must be concrete! Class: " + clazz.getSimpleName()); - T instance; - try - { - instance = clazz.getConstructor().newInstance(); - } - catch (ReflectiveOperationException e) - { - throw new IllegalArgumentException( - "Couldn't create instance of given " + clazz + ". Make sure default constructor is available", e); - } - + // Only try to instantiate or require concrete classes if there are non-static annotated methods + Supplier instanceSupplier = Suppliers.memoize(() -> { + if (Modifier.isAbstract(clazz.getModifiers())) + throw new IllegalArgumentException("Function class must be concrete to support non-static methods! Class: " + clazz.getSimpleName()); + try + { + return clazz.getConstructor().newInstance(); + } + catch (ReflectiveOperationException e) + { + throw new IllegalArgumentException( + "Couldn't create instance of given " + clazz + ". This is needed for non-static methods. Make sure default constructor is available", e); + } + }); Method[] methodz = clazz.getDeclaredMethods(); for (Method method : methodz) { @@ -126,7 +130,7 @@ public final class AnnotationParser if (method.getExceptionTypes().length != 0) throw new IllegalArgumentException("Annotated method '" + method.getName() +"', provided in '"+clazz+"' must not declare checked exceptions"); - ParsedFunction function = new ParsedFunction(method, instance); + ParsedFunction function = new ParsedFunction(method, clazz, instanceSupplier); functionList.add(function); } } @@ -160,7 +164,7 @@ public final class AnnotationParser private final int scarpetParamCount; private final Context.Type contextType; - private ParsedFunction(final Method method, final Object instance) + private ParsedFunction(Method method, Class originClass, Supplier instance) { this.name = method.getName(); this.isMethodVarArgs = method.isVarArgs(); @@ -190,12 +194,12 @@ public final class AnnotationParser maxParams = method.getAnnotation(ScarpetFunction.class).maxParams(); if (maxParams == UNDEFINED_PARAMS) throw new IllegalArgumentException("No maximum number of params specified for " + name + ", use ScarpetFunction.UNLIMITED_PARAMS for unlimited. " - + "Provided in " + instance.getClass()); + + "Provided in " + originClass); if (maxParams == ScarpetFunction.UNLIMITED_PARAMS) maxParams = Integer.MAX_VALUE; if (maxParams < this.minParams) throw new IllegalArgumentException("Provided maximum number of params for " + name + " is smaller than method's param count." - + "Provided in " + instance.getClass()); + + "Provided in " + originClass); } this.maxParams = maxParams; @@ -212,7 +216,7 @@ public final class AnnotationParser { MethodHandle tempHandle = MethodHandles.publicLookup().unreflect(method).asFixedArity().asSpreader(Object[].class, this.methodParamCount); tempHandle = tempHandle.asType(tempHandle.type().changeReturnType(Object.class)); - this.handle = Modifier.isStatic(method.getModifiers()) ? tempHandle : tempHandle.bindTo(instance); + this.handle = Modifier.isStatic(method.getModifiers()) ? tempHandle : tempHandle.bindTo(instance.get()); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); diff --git a/src/main/java/carpet/script/annotation/ScarpetFunction.java b/src/main/java/carpet/script/annotation/ScarpetFunction.java index edb980fd..745760b7 100644 --- a/src/main/java/carpet/script/annotation/ScarpetFunction.java +++ b/src/main/java/carpet/script/annotation/ScarpetFunction.java @@ -15,8 +15,8 @@ import carpet.script.value.Value; /** *

Defines a method that can be used as a function in the Scarpet language.

* - *

Methods annotated with this annotation are not required to accept and return the typical {@code Context context, Integer t, List lv}, but - * instead can specify whatever parameters they actually need that will be automatically converted from their respective {@link Value}s and passed to + *

Methods annotated with this annotation are not required to accept and return the implementation {@code Context context, Context.Type t, List lv}, + * but instead can specify whatever parameters they actually need that will be automatically converted from their respective {@link Value}s and passed to * the method as the expected type. Functions will automatically fail if given parameters are not compatible with the specified ones, or if the number * of provided arguments is either too large or too small.

* @@ -26,8 +26,7 @@ import carpet.script.value.Value; * In order to convert the output of your method to a {@link LazyValue} you will also need to register its conversion in {@link OutputConverter}

* *

In order for Carpet to find methods annotated with this annotation, you must add your function class(es) to Carpet by running - * {@link AnnotationParser#parseFunctionClass(Class)} ONCE. The provided {@link Class} must be concrete and provide the default constructor or an - * equivalent to it.

+ * {@link AnnotationParser#parseFunctionClass(Class)} ONCE.

* *

Methods annotated with this annotation must not declare throwing any checked exceptions.

*