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

org.comroid.api.Invocable Maven / Gradle / Ivy

The newest version!
package org.comroid.api;

import org.comroid.annotations.OptionalVararg;
import org.comroid.util.ReflectionHelper;
import org.jetbrains.annotations.ApiStatus.Internal;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

public interface Invocable {
    static  Invocable ofCallable(
            ThrowingSupplier callable
    ) {
        return ofProvider((Provider.Now) () -> {
            try {
                return callable.get();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        });
    }

    static  Invocable ofProvider(Provider provider) {
        return new Support.OfProvider<>(provider);
    }

    static  Invocable ofConsumer(Class type, Consumer consumer) {
        return new Support.OfConsumer<>(type, consumer);
    }

    @Deprecated
    static  Invocable ofMethodCall(Method method, @Nullable Object target) {
        return new Support.OfMethod<>(method, target);
    }

    static  Invocable ofMethodCall(Class inClass, String methodName) {
        return ofMethodCall(null, inClass, methodName);
    }

    static  Invocable ofMethodCall(@NotNull Object target, String methodName) {
        return ofMethodCall(target, target.getClass(), methodName);
    }

    static  Invocable ofMethodCall(@Nullable Object target, Class inClass, String methodName) {
        return Arrays.stream(inClass.getMethods())
                .filter(mtd -> mtd.getName().equals(methodName))
                .findAny()
                .map(mtd -> Invocable.ofMethodCall(target, mtd))
                .orElseThrow(() -> new NoSuchElementException(
                        String.format("Class %s does not have a method named %s", inClass, methodName)));
    }

    static  Invocable ofMethodCall(Method method) {
        return ofMethodCall((Object) null, method);
    }

    static  Invocable ofMethodCall(@Nullable Object target, Method method) {
        return new Support.OfMethod<>(method, target);
    }

    static  Invocable ofConstructor(Class type, @OptionalVararg Class... params) {
        Constructor[] constructors = type.getConstructors();

        if (constructors.length > 1) {
            return Arrays.stream(constructors)
                    .filter(it -> it.getParameterCount() == params.length)
                    .filter(it -> ReflectionHelper.matchingFootprint(it.getParameterTypes(), params))
                    .findAny()
                    .map(it -> Invocable.ofConstructor(Polyfill.uncheckedCast(it)))
                    .orElseThrow(() -> new NoSuchElementException("No Matching constructor could be found!"));
        } else {
            return ofConstructor(ReflectionHelper.findConstructor(type, params)
                    .orElseThrow(() -> new NoSuchElementException("No matching constructor found")));
        }
    }

    static  Invocable ofConstructor(Constructor constructor) {
        return new Support.OfConstructor<>(constructor);
    }

    static  Invocable paramReturning(Class type) {
        return new Support.ParamReturning<>(type);
    }

    static  Invocable constant(T value) {
        //noinspection unchecked
        return (Invocable) Support.Constant.Cache.computeIfAbsent(value, Support.Constant::new);
    }

    static  Invocable empty() {
        //noinspection unchecked
        return (Invocable) Support.Empty;
    }

    Class[] parameterTypesOrdered();

    @Nullable T invoke(Object... args) throws InvocationTargetException, IllegalAccessException;

    default T invokeAutoOrder(Object... args) throws InvocationTargetException, IllegalAccessException {
        return invoke(ReflectionHelper.arrange(args, parameterTypesOrdered()));
    }

    default T invokeRethrow(Object... args) {
        try {
            return invoke(args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    default  T invokeRethrow(Function remapper, Object... args) throws X {
        try {
            return invoke(args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw remapper.apply(e);
        }
    }

    default T autoInvoke(Object... args) {
        try {
            return invokeAutoOrder(args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    default @Nullable T silentAutoInvoke(Object... args) {
        try {
            return autoInvoke(args);
        } catch (Throwable ignored) {
            return null;
        }
    }

    default TypeMap typeMapped() {
        return this instanceof TypeMap ? (TypeMap) this : TypeMap.boxed(this);
    }

    default Supplier supplier() {
        class Adapter implements Supplier {
            @Override
            public T get() {
                return autoInvoke();
            }
        }

        return new Adapter();
    }

    default  Function function() {
        class Adapter implements Function {
            @Override
            public T apply(I i) {
                return autoInvoke(i);
            }
        }

        return new Adapter();
    }

    default  BiFunction biFunction() {
        class Adapter implements BiFunction {
            @Override
            public T apply(I1 i1, I2 i2) {
                return autoInvoke(i1, i2);
            }
        }

        return new Adapter();
    }

    interface TypeMap extends Invocable {
        static Map, Object> mapArgs(Object... args) {
            final long distinct = Stream.of(args)
                    .map(Object::getClass)
                    .distinct()
                    .count();

            if (distinct != args.length)
                throw new IllegalArgumentException("Duplicate argument types detected");

            final Map, Object> yield = new HashMap<>();

            for (Object arg : args) {
                yield.put(arg.getClass(), arg);
            }

            return yield;
        }

        static  TypeMap boxed(Invocable invocable) {
            return new TypeMap() {
                private final Invocable underlying = invocable;

                @Nullable
                @Override
                public T invoke(Map, Object> args) throws InvocationTargetException, IllegalAccessException {
                    if (underlying instanceof Support.OfMethod) {
                        final Method method = ((Support.OfMethod) underlying).method;
                        final Class[] param = method.getParameterTypes();
                        final AnnotatedType[] annParam = method.getAnnotatedParameterTypes();

                        for (int i = 0; i < param.length; i++) {
                            final AnnotatedType annotated = annParam[i];
                            final Class key = param[i];

                            if (args.containsKey(key) || !annotated.isAnnotationPresent(Null.class))
                                continue;
                            args.put(key, null);
                        }
                    }

                    return underlying.invokeAutoOrder(args.values().toArray());
                }

                @Override
                public Class[] parameterTypesOrdered() {
                    return underlying.parameterTypesOrdered();
                }
            };
        }

        @Override
        default @Nullable T invoke(Object... args) throws InvocationTargetException, IllegalAccessException {
            return invoke(mapArgs(args));
        }

        @Nullable T invoke(Map, Object> args) throws InvocationTargetException, IllegalAccessException;

        @Target(ElementType.PARAMETER)
        @Retention(RetentionPolicy.RUNTIME)
        @interface Null {
        }
    }

    abstract class Magic implements Invocable {
        private final Invocable underlying;

        protected Magic() {
            this.underlying = Invocable.ofMethodCall(ReflectionHelper.externalMethodsAbove(Magic.class, getClass())
                    .findAny()
                    .orElseThrow(() -> new NoSuchElementException("Could not find matching method")), this);
        }

        @Nullable
        @Override
        public T invoke(Object... args) {
            return underlying.autoInvoke(args);
        }

        @Override
        public Class[] parameterTypesOrdered() {
            return underlying.parameterTypesOrdered();
        }
    }

    @Internal
    final class Support {
        private static final Invocable Empty = constant(null);
        private static final Class[] NoClasses = new Class[0];

        private static final class OfProvider implements Invocable {
            private final Provider provider;

            public OfProvider(Provider provider) {
                this.provider = provider;
            }

            @Nullable
            @Override
            public T invoke(Object... args) {
                return provider.now();
            }

            @Override
            public Class[] parameterTypesOrdered() {
                return NoClasses;
            }
        }

        private static final class OfConstructor implements Invocable {
            private final Constructor constructor;

            public OfConstructor(Constructor constructor) {
                this.constructor = constructor;
            }

            @Override
            public @NotNull T invoke(Object... args) throws InvocationTargetException, IllegalAccessException {
                try {
                    return constructor.newInstance(args);
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public Class[] parameterTypesOrdered() {
                return constructor.getParameterTypes();
            }
        }

        private static final class OfMethod implements Invocable {
            private final Method method;
            private final Object target;

            private OfMethod(Method method, @Nullable Object target) {
                if (target == null && !Modifier.isStatic(method.getModifiers())) {
                    throw new IllegalArgumentException("Target cannot be null on non-static methods!",
                            new NullPointerException()
                    );
                }

                this.method = method;
                this.target = target;
            }

            @Nullable
            @Override
            public T invoke(Object... args) throws InvocationTargetException, IllegalAccessException {
                //noinspection unchecked
                return (T) method.invoke(target, args);
            }

            @Override
            public Class[] parameterTypesOrdered() {
                return method.getParameterTypes();
            }
        }

        private static final class ParamReturning implements Invocable {
            private final Class type;
            private final Class[] typeArray;

            private ParamReturning(Class type) {
                this.type = type;
                this.typeArray = new Class[]{type};
            }

            @Nullable
            @Override
            public T invoke(Object... args) {
                //noinspection unchecked
                return Stream.of(args)
                        .filter(type::isInstance)
                        .findAny()
                        .map(it -> (T) it)
                        .orElseThrow(() -> new NoSuchElementException(String.format("No parameter with type %s given",
                                type.getName()
                        )));
            }

            @Override
            public Class[] parameterTypesOrdered() {
                return typeArray;
            }
        }

        private static final class Constant implements Invocable {
            private static final Map> Cache = new ConcurrentHashMap<>();
            private final T value;

            private Constant(T value) {
                this.value = value;
            }

            @Nullable
            @Override
            public T invoke(Object... args) {
                return value;
            }

            @Override
            public Class[] parameterTypesOrdered() {
                return NoClasses;
            }
        }

        private static final class OfConsumer implements Invocable {
            private final Class argType;
            private final Consumer consumer;
            private final Class[] argTypeArr;

            private OfConsumer(Class argType, Consumer consumer) {
                this.argType = argType;
                this.consumer = consumer;
                this.argTypeArr = new Class[]{argType};
            }

            @Nullable
            @Override
            public T invoke(Object... args) {
                if (argType.isInstance(args[0])) {
                    consumer.accept(argType.cast(args[0]));
                    return null;
                } else {
                    throw new IllegalArgumentException(String.format("Invalid Type: %s",
                            args[0].getClass()
                                    .getName()
                    ));
                }
            }

            @Override
            public Class[] parameterTypesOrdered() {
                return argTypeArr;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy