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
© 2015 - 2024 Weber Informatics LLC | Privacy Policy