This class parses methods annotated with the {@link ScarpetFunction} annotation in a given {@link Class}, generating
* fully-featured, automatically parsed and converted functions to be used in the Scarpet language.
*
*
This class and the rest in this package will try to ensure that the annotated method receives the proper parameters
* directly, without all the always-repeated code of evaluating {@link Value}s, checking and casting them to their respective
* types, and converting them to the final needed object.
*
*
To do that, functions will save a list of {@link ValueConverter}s to convert all their parameters. {@link ValueConverter}s
* are able to convert from any compatible {@link Value} instance into the requested parameter type, as long as they are registered
* using their respective {@code register} functions.
*
*
Built-in {@link ValueConverter}s include but are not limited to converters to convert {@link List}s to actual Java lists while also
* converting every item inside of the {@link List} to the specified generic parameter ({@code <>}), with the same applying for maps
*
*
Parameters can be given the annotations (present in the {@link Locator} and {@link Param} interfaces) in order to restrict them or
* make them more permissive to accept types, such as {@link Param.AllowSingleton} for lists.
*
*
You can also declare optional parameters by using Java's {@link Optional} as the type of one of your parameters, though it must be
* at the end of the function, just before varargs if present and/or any other {@link Optional} parameters.
*
*
Output of the annotated methods will also be converted to a compatible {@link LazyValue} using the registered {@link OutputConverter}s,
* allowing to remove the need of explicitly converting to a {@link Value} and then to a {@link LazyValue} just to end the method (though you can
* return a {@link LazyValue} if you want.
*
*
For a variable argument count, the Java varargs notation can be used in the last parameter, converting the function into a variable argument
* function that will pass all the rest of parameters to that last varargs parameter, also converted into the specified type.
*
*
To begin, use the {@link #parseFunctionClass(Class)} method in a class with methods annotated with the {@link ScarpetFunction} annotation.
*
* @see ScarpetFunction
* @see Locator.Block
* @see Locator.Vec3d
* @see Param.Strict
* @see Param.AllowSingleton
* @see Param.KeyValuePairs
* @see Param.Custom
* @see Optional
* @see OutputConverter#registerToValue(Class, java.util.function.Function)
* @see OutputConverter#register(Class, java.util.function.Function)
* @see ValueCaster#register(Class, String)
* @see SimpleTypeConverter#registerType(Class, Class, java.util.function.Function, String)
* @see Param.Params#registerStrictConverter(Class, boolean, ValueConverter)
* @see Param.Params#registerCustomConverterFactory(java.util.function.BiFunction)
*/
public final class AnnotationParser
{
static final int UNDEFINED_PARAMS = -2;
private static final List functionList = new ArrayList<>();
/**
*
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()})
*
*
There is a set of requirements for the class and its methods:
*
*
Class must be concrete. That is, no interfaces or abstract classes should be passed
*
Class must have the default constructor (or an equivalent) available. That is done in order to not need the {@code static} modifier in every
* method, making them faster to code and simpler to look at.
*
Annotated methods must not throw checked exceptions. They can throw regular {@link RuntimeException}s (including but not limited to
* {@link InternalExpressionException}).
* Basically, it's fine as long as you don't add a {@code throws} declaration to your methods.
*
Varargs (or effectively varargs) annotated methods must explicitly declare a maximum number of parameters to ingest in the {@link ScarpetFunction}
* annotation. They can still declare an unlimited amount by setting that maximum to {@link ScarpetFunction#UNLIMITED_PARAMS}. "Effectively varargs"
* means a function that has at least a parameter requiring a {@link ValueConverter} that has declared {@link ValueConverter#consumesVariableArgs()}.
*
Annotated methods must not have a parameter with generics as the varargs parameter. This is just because it was painful for me (altrisi) and
* didn't want to support it. Those will crash with a {@code ClassCastException}
*
*
* @see ScarpetFunction
* @param The generic type of the class to parse.
* @param clazz The class to parse
*/
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);
}
Method[] methodz = clazz.getDeclaredMethods();
for (Method method : methodz)
{
if (!method.isAnnotationPresent(ScarpetFunction.class))
continue;
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);
functionList.add(function);
}
}
/**
*
Adds all parsed functions to the given {@link Expression}.
*
This is handled automatically by Carpet
*
* @param expr The expression to add every function to
*/
public static void apply(Expression expr)
{
for (ParsedFunction function : functionList)
expr.addLazyFunction(function.name, function.scarpetParamCount, function);
}
private static class ParsedFunction implements TriFunction, LazyValue>, UsageProvider
{
private final String name;
private final boolean isMethodVarArgs;
private final int methodParamCount;
private final List> valueConverters;
private final Class> varArgsType;
private final boolean primitiveVarArgs;
private final ValueConverter> varArgsConverter;
private final OutputConverter