All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.leeonky.dal.runtime.RuntimeContextBuilder Maven / Gradle / Ivy

package com.github.leeonky.dal.runtime;

import com.github.leeonky.dal.ast.opt.DALOperator;
import com.github.leeonky.dal.format.Formatter;
import com.github.leeonky.dal.runtime.checker.Checker;
import com.github.leeonky.dal.runtime.checker.CheckerSet;
import com.github.leeonky.dal.runtime.inspector.Dumper;
import com.github.leeonky.dal.runtime.inspector.DumperFactory;
import com.github.leeonky.dal.runtime.schema.Expect;
import com.github.leeonky.dal.type.ExtensionName;
import com.github.leeonky.dal.type.InputCode;
import com.github.leeonky.dal.type.Schema;
import com.github.leeonky.interpreter.RuntimeContext;
import com.github.leeonky.interpreter.SyntaxException;
import com.github.leeonky.util.*;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.github.leeonky.dal.runtime.ExpressionException.illegalOp2RuntimeException;
import static com.github.leeonky.dal.runtime.ExpressionException.illegalOperationRuntimeException;
import static com.github.leeonky.dal.runtime.schema.Actual.actual;
import static com.github.leeonky.dal.runtime.schema.Verification.expect;
import static com.github.leeonky.util.Classes.getClassName;
import static com.github.leeonky.util.CollectionHelper.toStream;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.STATIC;
import static java.util.Arrays.stream;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

public class RuntimeContextBuilder {
    private final ClassKeyMap> propertyAccessors = new ClassKeyMap<>();
    private final ClassKeyMap> dALCollectionFactories = new ClassKeyMap<>();
    private final ClassKeyMap> objectImplicitMapper = new ClassKeyMap<>();
    private final Map valueConstructors = new LinkedHashMap<>();
    private final Map> schemas = new HashMap<>();
    private final Set extensionMethods = new HashSet<>();
    private final Map> metaProperties = new HashMap<>();
    private final ClassKeyMap> remarks = new ClassKeyMap<>();
    private final ClassKeyMap> exclamations = new ClassKeyMap<>();
    private final List userDefinedLiterals = new ArrayList<>();
    private final NumberType numberType = new NumberType();
    private final Map, List>> curryingMethodArgRanges = new HashMap<>();
    private final Map> textFormatterMap = new LinkedHashMap<>();
    private final Map> operations = new HashMap<>();
    private Converter converter = Converter.getInstance();
    private final ClassKeyMap dumperFactories = new ClassKeyMap<>();
    private final CheckerSet checkerSetForMatching = new CheckerSet(CheckerSet::defaultMatching);
    private final CheckerSet checkerSetForEqualing = new CheckerSet(CheckerSet::defaultEqualing);
    //    private final
    private int maxDumpingLineSize = 2000;
    private int maxDumpingObjectSize = 255;
    private ErrorHook errorHook = (i, code, e) -> {
    };
    private final Map, Map>> localMetaProperties
            = new TreeMap<>(Classes::compareByExtends);

    public RuntimeContextBuilder registerMetaProperty(Object property, Function function) {
        metaProperties.put(property, function);
        return this;
    }

    public RuntimeContextBuilder registerTextFormatter(String name, TextFormatter formatter) {
        textFormatterMap.put(name, formatter);
        return this;
    }

    public DALRuntimeContext build(Object inputValue) {
        return build(() -> inputValue, null);
    }

    public DALRuntimeContext build(InputCode inputSupplier) {
        return build(inputSupplier, null);
    }

    public DALRuntimeContext build(InputCode inputSupplier, Class rootSchema) {
        if (inputSupplier == null)
            return new DALRuntimeContext(() -> null, rootSchema);
        return new DALRuntimeContext(inputSupplier, rootSchema);
    }

    public RuntimeContextBuilder registerValueFormat(Formatter formatter) {
        return registerValueFormat(formatter.getFormatterName(), formatter);
    }

    @SuppressWarnings("unchecked")
    public RuntimeContextBuilder registerValueFormat(String name, Formatter formatter) {
        valueConstructors.put(name, (o, c) -> ((Formatter) formatter).transform(o.instance()));
        return this;
    }

    public RuntimeContextBuilder registerSchema(Class schema) {
        return registerSchema(NameStrategy.SIMPLE_NAME, schema);
    }

    @SuppressWarnings("unchecked")
    public RuntimeContextBuilder registerSchema(String name, Class schema) {
        schemas.put(name, BeanClass.create(schema));
        return registerSchema(name, (data, context) ->
                expect(new Expect(BeanClass.create((Class) schema), null)).verify(context, actual(data)));
    }

    public RuntimeContextBuilder registerSchema(String name, BiFunction predicate) {
        valueConstructors.put(name, (o, context) -> {
            if (predicate.apply(o, context))
                return o.instance();
            throw new IllegalTypeException();
        });
        return this;
    }

    @SuppressWarnings("unchecked")
    public  RuntimeContextBuilder registerPropertyAccessor(Class type, PropertyAccessor propertyAccessor) {
        propertyAccessors.put(type, (PropertyAccessor) propertyAccessor);
        return this;
    }

    @SuppressWarnings("unchecked")
    public  RuntimeContextBuilder registerDALCollectionFactory(Class type, DALCollectionFactory DALCollectionFactory) {
        dALCollectionFactories.put(type, (DALCollectionFactory) DALCollectionFactory);
        return this;
    }

    public RuntimeContextBuilder registerSchema(NameStrategy nameStrategy, Class schema) {
        return registerSchema(nameStrategy.toName(schema), schema);
    }

    public RuntimeContextBuilder registerStaticMethodExtension(Class staticMethodExtensionClass) {
        Stream.of(staticMethodExtensionClass.getMethods()).filter(method -> method.getParameterCount() >= 1
                                                                            && (STATIC & method.getModifiers()) != 0).forEach(extensionMethods::add);
        return this;
    }

    @SuppressWarnings("unchecked")
    public  RuntimeContextBuilder registerImplicitData(Class type, Function mapper) {
        objectImplicitMapper.put(type, (Function) mapper);
        return this;
    }

    public Converter getConverter() {
        return converter;
    }

    public RuntimeContextBuilder setConverter(Converter converter) {
        this.converter = converter;
        return this;
    }

    public RuntimeContextBuilder registerUserDefinedLiterals(UserLiteralRule rule) {
        userDefinedLiterals.add(rule);
        return this;
    }

    public RuntimeContextBuilder registerCurryingMethodAvailableParameters(Method method, BiFunction, List> range) {
        curryingMethodArgRanges.put(method, range);
        return this;
    }

    private Set methodToCurrying(Class type, Object methodName) {
        return Stream.of(stream(type.getMethods()).filter(method -> !Modifier.isStatic(method.getModifiers()))
                                .filter(method -> method.getName().equals(methodName)),
                        staticMethodsToCurrying(type, methodName, Object::equals),
                        staticMethodsToCurrying(type, methodName, Class::isAssignableFrom))
                .flatMap(Function.identity()).collect(Collectors.toCollection(LinkedHashSet::new));
    }

    private Stream staticMethodsToCurrying(Class type, Object property,
                                                   BiPredicate, Class> condition) {
        return extensionMethods.stream()
                .filter(method -> staticExtensionMethodName(method).equals(property))
                .filter(method -> condition.test(method.getParameters()[0].getType(), type));
    }

    private static String staticExtensionMethodName(Method method) {
        ExtensionName extensionName = method.getAnnotation(ExtensionName.class);
        return extensionName != null ? extensionName.value() : method.getName();
    }

    BiFunction, List> fetchCurryingMethodArgRange(Method method) {
        return curryingMethodArgRanges.get(method);
    }

    public CheckerSet checkerSetForMatching() {
        return checkerSetForMatching;
    }

    public CheckerSet checkerSetForEqualing() {
        return checkerSetForEqualing;
    }

    public RuntimeContextBuilder registerDumper(Class type, DumperFactory factory) {
        dumperFactories.put(type, factory);
        return this;
    }

    public void setMaxDumpingLineSize(int size) {
        maxDumpingLineSize = size;
    }

    public  RuntimeContextBuilder registerErrorHook(ErrorHook hook) {
        errorHook = Objects.requireNonNull(hook);
        return this;
    }

    public void mergeTextFormatter(String name, String other, String... others) {
        TextFormatter formatter = textFormatterMap.get(other);
        for (String o : others)
            formatter = formatter.merge(textFormatterMap.get(o));
        registerTextFormatter(name, delegateFormatter(formatter, "Merged from " + other + " " + String.join(" ", others)));
    }

    private TextFormatter delegateFormatter(TextFormatter formatter, final String description) {
        return new TextFormatter() {
            @Override
            protected Object format(Object content, TextAttribute attribute, DALRuntimeContext context) {
                return formatter.format(content, attribute, context);
            }

            @Override
            protected TextAttribute attribute(TextAttribute attribute) {
                return formatter.attribute(attribute);
            }

            @Override
            public Class returnType() {
                return formatter.returnType();
            }

            @Override
            public Class acceptType() {
                return formatter.acceptType();
            }

            @Override
            public String description() {
                return description;
            }
        };
    }

    public RuntimeContextBuilder registerMetaProperty(Class type, Object name, Function function) {
        localMetaProperties.computeIfAbsent(type, k -> new HashMap<>()).put(name, function);
        return this;
    }

    public RuntimeContextBuilder registerDataRemark(Class type, Function action) {
        remarks.put(type, action);
        return this;
    }

    public RuntimeContextBuilder registerExclamation(Class type, Function action) {
        exclamations.put(type, action);
        return this;
    }

    public RuntimeContextBuilder registerOperator(Operators operator, Operation operation) {
        operations.computeIfAbsent(operator, o -> new LinkedList<>()).addFirst(operation);
        return this;
    }

    public BeanClass schemaType(String schema) {
        BeanClass type = schemas.get(schema);
        if (type != null)
            return type;
        throw new IllegalStateException(format("Unknown schema '%s'", schema));
    }

    public void setMaxDumpingObjectSize(int maxDumpingObjectSize) {
        this.maxDumpingObjectSize = maxDumpingObjectSize;
    }

    public class DALRuntimeContext implements RuntimeContext {
        private final LinkedList stack = new LinkedList<>();
        private final Map partialPropertyStacks;
        private final Data inputValue;
        private final Throwable inputError;

        public DALRuntimeContext(InputCode supplier, Class schema) {
            BeanClass rootSchema = null;
            if (schema != null)
                rootSchema = BeanClass.create(schema);
            Data value;
            Throwable error;
            try {
                value = wrap(supplier.get(), rootSchema);
                error = null;
            } catch (Throwable e) {
                value = wrap(null);
                error = e;
            }
            inputError = error;
            inputValue = value;
            partialPropertyStacks = new HashMap<>();
        }

        public Data getThis() {
            if (stack.isEmpty()) {
                if (inputError != null)
                    throw new InputException(inputError);
                return inputValue;
            }
            return stack.getFirst();
        }

        public  T pushAndExecute(Data data, Supplier supplier) {
            try {
                stack.push(data);
                return supplier.get();
            } finally {
                stack.pop();
            }
        }

        public Optional searchValueConstructor(String type) {
            return Optional.ofNullable(valueConstructors.get(type));
        }

        public Set findPropertyReaderNames(Object instance) {
            return getObjectPropertyAccessor(instance).getPropertyNames(instance);
        }

        private PropertyAccessor getObjectPropertyAccessor(Object instance) {
            return propertyAccessors.tryGetData(instance)
                    .orElseGet(() -> new JavaClassPropertyAccessor<>(BeanClass.createFrom(instance)));
        }

        public Boolean isNull(Object instance) {
            return propertyAccessors.tryGetData(instance).map(f -> f.isNull(instance))
                    .orElseGet(() -> Objects.equals(instance, null));
        }

        public Object getPropertyValue(Data data, Object property) {
            PropertyAccessor propertyAccessor = getObjectPropertyAccessor(data.instance());
            try {
                return propertyAccessor.getValueByData(data, property);
            } catch (InvalidPropertyException e) {
                return data.currying(property).orElseThrow(() -> e).resolve();
            } catch (InvocationException e) {
                throw e;
            } catch (Exception e) {
                throw new InvocationException(e);
            }
        }

        public DALCollection createCollection(Object instance) {
            return dALCollectionFactories.tryGetData(instance).map(factory -> factory.create(instance))
                    .orElseGet(() -> new CollectionDALCollection<>(toStream(instance).collect(toList())));
        }

        public boolean isRegisteredList(Object instance) {
            return dALCollectionFactories.tryGetData(instance).map(f -> f.isList(instance)).orElse(false);
        }

        public Converter getConverter() {
            return converter;
        }

        public Data wrap(Object instance) {
            return wrap(instance, null);
        }

        public Data wrap(Object instance, String schema, boolean isList) {
            BeanClass schemaType = schemas.get(schema);
            if (isList && schemaType != null)
                schemaType = BeanClass.create(Array.newInstance(schemaType.getType(), 0).getClass());
            return wrap(instance, schemaType);
        }

        public Data wrap(Object instance, BeanClass schemaType) {
            return new Data(instance, this, SchemaType.create(schemaType));
        }

        public Optional takeUserDefinedLiteral(String token) {
            return userDefinedLiterals.stream().map(userLiteralRule -> userLiteralRule.compile(token))
                    .filter(Result::hasResult)
                    .findFirst();
        }

        public void appendPartialPropertyReference(Data data, Object symbol) {
            fetchPartialProperties(data).map(partialProperties -> partialProperties.appendPartialProperties(symbol));
        }

        private Optional fetchPartialProperties(Data data) {
            return partialPropertyStacks.values().stream().map(partialPropertyStack ->
                    partialPropertyStack.fetchPartialProperties(data)).filter(Objects::nonNull).findFirst();
        }

        public void initPartialPropertyStack(Data instance, Object prefix, Data partial) {
            partialPropertyStacks.computeIfAbsent(instance, _key -> fetchPartialProperties(instance)
                    .map(partialProperties -> partialProperties.partialPropertyStack)
                    .orElseGet(PartialPropertyStack::new)).setupPartialProperties(prefix, partial);
        }

        public Set collectPartialProperties(Data instance) {
            PartialPropertyStack partialPropertyStack = partialPropertyStacks.get(instance);
            if (partialPropertyStack != null)
                return partialPropertyStack.collectPartialProperties(instance);
            return fetchPartialProperties(instance).map(partialProperties ->
                    partialProperties.partialPropertyStack.collectPartialProperties(instance)).orElse(emptySet());
        }

        public NumberType getNumberType() {
            return numberType;
        }

        public Optional getImplicitObject(Object obj) {
            return objectImplicitMapper.tryGetData(obj).map(mapper -> mapper.apply(obj));
        }

        public Set methodToCurrying(Class type, Object methodName) {
            return RuntimeContextBuilder.this.methodToCurrying(type, methodName);
        }

        public Function fetchGlobalMetaFunction(MetaData metaData) {
            return metaProperties.computeIfAbsent(metaData.name(), k -> {
                throw illegalOp2RuntimeException(format("Meta property `%s` not found", metaData.name()));
            });
        }

        private Optional> fetchLocalMetaFunction(MetaData metaData) {
            return metaFunctionsByType(metaData).map(e -> {
                metaData.addCallType(e.getKey());
                return e.getValue().get(metaData.name());
            }).filter(Objects::nonNull).findFirst();
        }

        public Optional> fetchSuperMetaFunction(MetaData metaData) {
            return metaFunctionsByType(metaData)
                    .filter(e -> !metaData.calledBy(e.getKey()))
                    .map(e -> {
                        metaData.addCallType(e.getKey());
                        return e.getValue().get(metaData.name());
                    }).filter(Objects::nonNull).findFirst();
        }

        private Stream, Map>>> metaFunctionsByType(MetaData metaData) {
            return localMetaProperties.entrySet().stream().filter(e -> metaData.isInstance(e.getKey()));
        }

        @SuppressWarnings("unchecked")
        public  TextFormatter fetchFormatter(String name, int position) {
            return (TextFormatter) textFormatterMap.computeIfAbsent(name, attribute -> {
                throw new SyntaxException(format("Invalid text formatter `%s`, all supported formatters are:\n%s",
                        attribute, textFormatterMap.entrySet().stream().map(e -> format("  %s:\n    %s",
                                e.getKey(), e.getValue().fullDescription())).collect(joining("\n"))), position);
            });
        }

        public Checker fetchEqualsChecker(Data expected, Data actual) {
            return checkerSetForEqualing.fetch(expected, actual);
        }

        public Checker fetchMatchingChecker(Data expected, Data actual) {
            return checkerSetForMatching.fetch(expected, actual);
        }

        public Dumper fetchDumper(Data data) {
            return dumperFactories.tryGetData(data.instance()).map(factory -> factory.apply(data)).orElseGet(() -> {
                if (data.isNull())
                    return (_data, dumpingContext) -> dumpingContext.append("null");
                if (data.isList())
                    return Dumper.LIST_DUMPER;
                return Dumper.MAP_DUMPER;
            });
        }

        public int maxDumpingLineCount() {
            return maxDumpingLineSize;
        }

        public int maxDumpingObjectSize() {
            return maxDumpingObjectSize;
        }

        public void hookError(ThrowingSupplier input, String expression, Throwable error) {
            errorHook.handle(input, expression, error);
        }

        public Object invokeMetaProperty(MetaData metaData) {
            return fetchLocalMetaFunction(metaData).orElseGet(() -> fetchGlobalMetaFunction(metaData)).apply(metaData);
        }

        public Data invokeDataRemark(RemarkData remarkData) {
            Object instance = remarkData.data().instance();
            return remarks.tryGetData(instance)
                    .orElseThrow(() -> illegalOperationRuntimeException("Not implement operator () of " + Classes.getClassName(instance)))
                    .apply(remarkData);
        }

        public Data invokeExclamations(ExclamationData exclamationData) {
            Object instance = exclamationData.data().instance();
            return exclamations.tryGetData(instance)
                    .orElseThrow(() -> illegalOp2RuntimeException(format("Not implement operator %s of %s",
                            exclamationData.label(), Classes.getClassName(instance))))
                    .apply(exclamationData);
        }

        public Data calculate(Data v1, DALOperator opt, Data v2) {
            for (Operation operation : operations.get(opt.overrideType()))
                if (operation.match(v1, opt, v2, this))
                    return operation.operate(v1, opt, v2, this);
            throw illegalOperationRuntimeException(format("No operation `%s` between '%s' and '%s'", opt.overrideType(),
                    getClassName(v1.instance()), getClassName(v2.instance())));
        }
    }
}