Files
fabric-carpet/src/main/java/carpet/script/annotation/MapConverter.java
T
altrisi cc7a9c01be Annotated Scarpet (#772)
* Progress by now.

* Output conversions plus a few extra random things

* More progress, mostly list converting and a way to get converters

* Apply ValueConverters, refactor valueconverter getter to

accept types with annotations and generics, made listconverter pass
those params and annotations recursively. And probably more things.

* Some extra docs, the 100% most important thing

* Added MapConverter, strict Strings and Texts, some docs additions

and commented out heuristics

* Rework StrictParam to use SimpleTypeConverters and be saved in a map

* Minor changes and docs & added non-fully-functional context "converter"

* Added support for variable value count converters, multi-value eaters &

0 value eaters (Context).

* Docs update, enable Context converter, fix minor TODOs

* Move (most) parameter annotations into a single class,

remove deprecated ValueCaster#cast, update some docs.

* Small refactor of Param.Strict registering, added boolean to it

* Added PairConverter, moved Context and LV ValueConverters to Params

* Document public methods of AnnotationParser

* Add a lot more docs, make it ready for the first preview PR

* Move ValueConverter population outside of makeFunction,

check lv size for > max arguments, minor doc updates and workaround for
Mercury#28 until released

* Added optional parameters (declared using Java's Optional),

made evalAndConvert check for the presence of the next iteration,
updated test class

* Generalize Value OutputConverter

* ListConverter and MapConverter now follow ValueConverter contract of

incorrect value handling, returning a complete null for an incorrect
value anywhere. Also small fix to OptionalConverter and docs.
Getting ready for checking null in AnnotationParser!

* Change reflection's #invoke to a MethodHandle to boost speed by a lot

Commented

* Dedupe code, update and remove outdated todos, add 2 new ValueCasters

* Merge lambdas in display_title2 test function to be more accurate

* Check nulls somehow, I guess

* Move base function logic into a class to stop

passing hundreds of parameters into static functions and therefore simplify it

* Reduce visibility and fix a bug with PairConverter

* Hide Context provider in usage report

* Add support for primitive varargs

* Added TheLazyT converter

* Drop #getPrefixedTypeName and use 'something' as name of values

* Use value again because of varargs, document that

* Remove a completed TODO

* Remove old disabled heuristic value type name getter

* Fix TheLazyT

* Preparing locators before touching current Carpet infrastructure

* Add notes about non-implementedness of remaining locators

* Added APIs to register custom ValueConverters

* Mention new APIs in javadocs

* Adapt to new Carpet changes and deprecated non-implemented annotations

* Update example function for display_title()

* Better implementation for player_list example

* Fix example error message

* FunctionArgument no longer has generics

* Modernize Text converters

* OutputConverter is final, plus other small changes

* Few non-behavioural changes

* Rename LazyFunction to ScarpetFunction

* Drop LazyValue in parameters, not in return

Updating docs was a nightmare

* CARPET CHANGE - Make Fluff.AbstractLazyFunction.unpackArgs call unpackLazy (was duped)

* Make errors more like current Scarpet errors

* Allow customizing ContextType when evaluating arguments

* Add a few missing docs, comment out outdated ValueCaster doc

It's been a while, wow

* Fix for new Carpet signature :irritatered:

Please don't break API methods! Extensions may be using them, and they
will just crash the game or behave in unexpected ways else!

* Format all code to Carpet's. Powered by Eclipse

Reviewed to be sure it's ok. No functional changes, except from a small correction

* Always use full name for Context.Type

* Add parsing for all current Scarpet api and language classes

And remove test functions

* Allow void return type

* Fix no param methods

Whoops!

* Remove outdated comment and fix small javadoc linting problem

* Drop Param.TheLazyT in favour of just Context.Type type

And small performance improvement to varargs varargs since fastutil's toArray is crap

* Quote functions in error messages

* Make SimpleTypeConverter independent of ValueCaster

'Real world' usage demonstrated it can be annoying else, mostly for wrapper only values where you just want the wrapped object
2021-05-21 01:33:00 -03:00

164 lines
6.9 KiB
Java

package carpet.script.annotation;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import carpet.script.Context;
import carpet.script.value.ListValue;
import carpet.script.value.MapValue;
import carpet.script.value.Value;
/**
* <p>Converts a {@link MapValue} to a {@link Map}, converting all of its contents to their respective types.</p>
*
* <p>If the {@link Param.KeyValuePairs} annotation is specified, uses its subclass at {@link PairConverter} and allows passing either a map, a list
* like [key, value, key2, value2,...] or the same as the list inlined in the function's body, unless {@link Param.KeyValuePairs#allowMultiparam()} is
* set to {@code false}.</p>
*
* <p>Maps provided by this converter are <b>not</b> linked to the initial map, and therefore will not reflect changes in either of them.</p>
*
* @param <K> The type of the map's keys
* @param <V> The type of the map's values
*/
class MapConverter<K, V> implements ValueConverter<Map<K, V>>
{
protected final ValueConverter<K> keyConverter;
protected final ValueConverter<V> valueConverter;
@Override
public String getTypeName()
{
return "map with " + keyConverter.getTypeName() + "s as the key and " + valueConverter.getTypeName() + "s as the value";
}
@Override
public Map<K, V> convert(Value value)
{
Map<K, V> result = new HashMap<>();
if (value instanceof MapValue)
{
for (Entry<Value, Value> entry : ((MapValue) value).getMap().entrySet())
{
K key = keyConverter.convert(entry.getKey());
V val = valueConverter.convert(entry.getValue());
if (key == null || val == null)
return null;
result.put(key, val);
}
return result;
}
return null;
}
private MapConverter(AnnotatedType keyType, AnnotatedType valueType)
{
keyConverter = ValueConverter.fromAnnotatedType(keyType);
valueConverter = ValueConverter.fromAnnotatedType(valueType);
}
/**
* <p>Returns a new {@link MapConverter} to convert to the given {@link AnnotatedType}.</p>
*
* <p>The returned {@link ValueConverter} will convert the objects inside the map (keys and values) to the generics specified in the
* {@link AnnotatedType}.</p>
*
* <p>If the provided {@link AnnotatedType} is annotated with {@link Param.KeyValuePairs}, it will return an implementation of
* {@link MapConverter} that provides extra checks in order to allow inputs of either {@code {k->v,k2->v2,...}}, {@code [k,v,k2,v2,...]} or
* {@code fn(...,k,v,k2,v2,...)}</p>
*
* @apiNote This method expects the {@link AnnotatedType} to already be of {@link Map} type, and, while it will technically accept a
* non-{@link Map} {@link AnnotatedType}, it will fail if it doesn't has at least two generic parameters with an
* {@link ArrayIndexOutOfBoundsException}.
* @param annotatedType The type to get generics information from
* @return A new {@link MapConverter} for the data specified in the {@link AnnotatedType}
*/
static MapConverter<?, ?> fromAnnotatedType(AnnotatedType annotatedType)
{
AnnotatedType[] annotatedGenerics = ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
return annotatedType.isAnnotationPresent(Param.KeyValuePairs.class)
? new PairConverter<>(annotatedGenerics[0], annotatedGenerics[1], annotatedType.getAnnotation(Param.KeyValuePairs.class))
: new MapConverter<>(annotatedGenerics[0], annotatedGenerics[1]);
}
private static final class PairConverter<K, V> extends MapConverter<K, V>
{
private final boolean acceptMultiParam;
private PairConverter(AnnotatedType keyType, AnnotatedType valueType, Param.KeyValuePairs config)
{
super(keyType, valueType);
acceptMultiParam = config.allowMultiparam();
}
@Override
public boolean consumesVariableArgs()
{
return acceptMultiParam;
}
@Override
public Map<K, V> convert(Value value) {
return value instanceof MapValue ? super.convert(value)
: value instanceof ListValue ? convertList(((ListValue)value).getItems())
: null; // Multiparam mode can only be used in evalAndConvert
}
private Map<K, V> convertList(List<Value> valueList)
{
if (valueList.size() % 2 == 1)
return null;
Map<K, V> map = new HashMap<>();
Iterator<Value> val = valueList.iterator();
while (val.hasNext())
{
K key = keyConverter.convert(val.next());
V value = valueConverter.convert(val.next());
if (key == null || value == null)
return null;
map.put(key, value);
}
return map;
}
@Override
public Map<K, V> checkAndConvert(Iterator<Value> valueIterator, Context context, Context.Type theLazyT)
{
if (!valueIterator.hasNext())
return null;
Value val = valueIterator.next();
if (!acceptMultiParam || val instanceof MapValue || (val instanceof ListValue && !(keyConverter instanceof ListConverter)))
return convert(val); // @KeyValuePairs Map<List<Something>, Boolean> will not support list consumption
Map<K, V> map = new HashMap<>();
K key = keyConverter.convert(val); //First pair is manual since we got it to check for a different conversion mode
V value = valueConverter.checkAndConvert(valueIterator, context, theLazyT);
if (key == null || value == null)
return null;
map.put(key, value);
while (valueIterator.hasNext())
{
key = keyConverter.checkAndConvert(valueIterator, context, theLazyT);
value = valueConverter.checkAndConvert(valueIterator, context, theLazyT);
if (key == null || value == null)
return null;
map.put(key, value);
}
return map;
}
@Override
public String getTypeName()
{
return "either a map of key-value pairs" + (acceptMultiParam ? "," : " or") + " a list in the form of [key, value, key2, value2,...]"
+ (acceptMultiParam ? " or those key-value pairs in the function" : "") + " (keys being " + keyConverter.getTypeName()
+ "s and values being " + valueConverter.getTypeName() + "s)";
}
}
}