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.

*