package carpet.script.annotation; import java.lang.reflect.AnnotatedType; import java.lang.reflect.ParameterizedType; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import org.apache.commons.lang3.ClassUtils; import carpet.script.Context; import carpet.script.annotation.Param.Params; import carpet.script.value.Value; import org.jspecify.annotations.Nullable; /** *

Classes implementing this interface are able to convert {@link Value} instances into {@code }, in order to easily use them in parameters for * Scarpet functions created using the {@link ScarpetFunction} annotation.

* * @param The result type that the passed {@link Value}s will be converted to */ public interface ValueConverter { /** *

Returns the the user-friendly name of the result this {@link ValueConverter} converts to, without {@code a} or {@code an}, and without * capitalizing the first letter.

* *

This method can return {@code null}, in which case users of this function should hide this {@link ValueConverter} from any aids or usages of * the function, meaning that the {@link ValueConverter} is only providing some meta information that isn't directly provided through the Scarpet * language.

* *

Those aids calling this method may append an {@code s} to the return value of this method, in case the type is used in places where more * than one may be present, such as lists or varargs.

* * @apiNote This method is intended to only be called when an error has occurred and therefore there is a need to print a stacktrace with some * helpful usage instructions. */ @Nullable String getTypeName(); /** *

Converts the given {@link Value} to {@code }, which was defined when being registered.

* *

Returns {@code null} if one of the conversions failed, either because the {@link Value} was incompatible in some position of the chain, or * because the actual converting function returned {@code null} (which usually only occurs when the {@link Value} is incompatible/does not hold * the appropriate information)

* *

Functions using the converter can use {@link #getTypeName()} to get the name of the type this was trying to convert to, in case they are not * trying to convert to anything else, where it would be recommended to tell the user the name of the final type instead.

* * @param value The {@link Value} to convert * @param context The {@link Context} of the call * @return The converted value, or {@code null} if the conversion failed in the process * @apiNote

While most implementations of this method should and will return the type from this method, implementations that require * parameters from {@link #checkAndConvert(Iterator, Context, Context.Type)} or that require multiple parameters may decide to throw * {@link UnsupportedOperationException} in this method and override {@link #checkAndConvert(Iterator, Context, Context.Type)} instead. Those * implementations, however, should not be available for map or list types, since those can only operate with {@link Value}.

*

Currently, the only implementations requiring that are {@link Params#CONTEXT_PROVIDER} and {@link Params#CONTEXT_TYPE_PROVIDER}

*

Implementations can also provide different implementations for this and {@link #checkAndConvert(Iterator, Context, Context.Type)}, in case * they can support it in some situations that can't be used else, such as inside of lists or maps, although they should try to provide * in {@link #checkAndConvert(Iterator, Context, Context.Type)} at least the same conversion as the one from this method.

*

Even with the above reasons, {@link ValueConverter} users should try to implement {@link #convert(Value, Context)} whenever possible instead of * {@link #checkAndConvert(Iterator, Context, Context.Type)}, since it allows its usage in generics of lists and maps.

*/ @Nullable R convert(Value value, @Nullable Context context); /** * Old version of {@link #convert(Value)} without taking a {@link Context}.

* * This shouldn't be used given converters now take a context in the convert function to allow for converting * values in lists or other places without using static state.

* * @param value The value to convert * @return A converted value * @deprecated Calling this method instead of {@link #convert(Value, Context)} may not return values for some converters */ @Nullable @Deprecated(forRemoval = true) default R convert(Value value) { try { return convert(value, null); } catch (NullPointerException e) { return null; } } /** *

Returns whether this {@link ValueConverter} consumes a variable number of elements from the {@link Iterator} passed to it via * {@link #checkAndConvert(Iterator, Context, Context.Type)}.

* * @implNote The default implementation returns {@code false} * @see #valueConsumption() */ default boolean consumesVariableArgs() { return false; } /** *

Declares the number of {@link Value}s this converter consumes from the {@link Iterator} passed to it in * {@link #checkAndConvert(Iterator, Context, Context.Type)}.

* *

If this {@link ValueConverter} can accept a variable number of arguments (therefore the result of calling {@link #consumesVariableArgs()} * must return {@code true}), it will return the minimum number of arguments it will consume.

* * @implNote The default implementation returns {@code 1} * */ default int valueConsumption() { return 1; } /** *

Gets the proper {@link ValueConverter} for the given {@link AnnotatedType}, considering the type of {@code R[]} as {@code R}.

* *

This function does not only consider the actual type (class) of the passed {@link AnnotatedType}, but also its annotations and generic * parameters in order to get the most specific {@link ValueConverter}.

* *

Some processing is delegated to the appropriate implementations of {@link ValueConverter} in order to get registered converters or generate * specific ones for some functions.

* * @param The type of the class the returned {@link ValueConverter} will convert to. It is declared from the type in the * {@link AnnotatedType} directly inside the function. * @param annoType The {@link AnnotatedType} to search a {@link ValueConverter}. * @return A usable {@link ValueConverter} to convert from a {@link Value} to {@code } */ @SuppressWarnings("unchecked") static ValueConverter fromAnnotatedType(AnnotatedType annoType) { Class type = annoType.getType() instanceof ParameterizedType ? // We are defining R here. (Class) ((ParameterizedType) annoType.getType()).getRawType() : (Class) annoType.getType(); // I (altrisi) won't implement generics in varargs. Those are just PAINFUL. They have like 3-4 nested types and don't have the generics // and annotations in the same place, plus they have a different "conversion hierarchy" than the rest, making everything require // special methods to get the class from type, generics from type and annotations from type. Not worth the effort for me. // Example: AnnotatedGenericTypeArray (or similar) being (@Paran.KeyValuePairs Map... name) // Those will just fail with a ClassCastException. if (type.isArray()) { type = (Class) type.getComponentType(); // Varargs } type = (Class) ClassUtils.primitiveToWrapper(type); // It will be boxed anyway, this saves unboxing-boxing if (type == List.class) { return (ValueConverter) ListConverter.fromAnnotatedType(annoType); // Already checked that type is List } if (type == Map.class) { return (ValueConverter) MapConverter.fromAnnotatedType(annoType); // Already checked that type is Map } if (type == Optional.class) { return (ValueConverter) OptionalConverter.fromAnnotatedType(annoType); } if (annoType.getDeclaredAnnotations().length != 0) { if (annoType.isAnnotationPresent(Param.Custom.class)) { return Params.getCustomConverter(annoType, type); // Throws if incorrect usage } if (annoType.isAnnotationPresent(Param.Strict.class)) { return (ValueConverter) Params.getStrictConverter(annoType); // Throws if incorrect usage } if (annoType.getAnnotations()[0].annotationType().getEnclosingClass() == Locator.class) { return Locator.Locators.fromAnnotatedType(annoType, type); } } // Class only checks if (Value.class.isAssignableFrom(type)) { return Objects.requireNonNull(ValueCaster.get(type), "Value subclass " + type + " is not registered. Register it in ValueCaster to use it"); } if (type == Context.class) { return (ValueConverter) Params.CONTEXT_PROVIDER; } if (type == Context.Type.class) { return (ValueConverter) Params.CONTEXT_TYPE_PROVIDER; } return Objects.requireNonNull(SimpleTypeConverter.get(type), "Type " + type + " is not registered. Register it in SimpleTypeConverter to use it"); } /** *

Checks for the presence of the next {@link Value} in the given {@link Iterator}, evaluates it with the given {@link Context} and then * converts it to this {@link ValueConverter}'s output type.

* *

This should be the preferred way to call the converter, since it allows multi-param converters and allows meta converters (such as the {@link Context} provider)

* * @implSpec Implementations of this method are not required to move the {@link Iterator} to the next position, such as in the case of meta * providers like {@link Context}.

Implementations can also use more than a single parameter when being called with this function, * but in such case they must implement {@link #valueConsumption()} to return how many parameters do they consume at minimum, and, if * they may consume variable arguments, implement {@link #consumesVariableArgs()}

This method holds the same nullability * constraints as {@link #convert(Value, Context)}

* @param valueIterator An {@link Iterator} holding the {@link Value} to convert in next position * @param context The {@link Context} this function has been called with * @param contextType The {@link Context.Type} that the original function was called with * @return The next {@link Value} (s) converted to the type {@code } of this {@link ValueConverter} * @implNote This method's default implementation runs the {@link #convert(Value, Context)} function in the next {@link Value} ignoring {@link Context} and * {@code theLazyT}. */ @Nullable default R checkAndConvert(Iterator valueIterator, Context context, Context.Type contextType) { return !valueIterator.hasNext() ? null : convert(valueIterator.next(), context); } }