package uk.co.nickthecoder.feather.runtime;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* Feather uses this to hold function objects.
* i.e. functions are first class citizens in Feather (they aren't in Java).
* You can assign a function to a variable, and pass functions into other functions/methods.
* <p>
* Parameter types A1..A9 are the function's parameter types.
* For functions with fewer than 9 parameters, the later ones are of type [Void].
* <p>
* Note that functions with more than 9 arguments are not supported.
* This means methods with more than 8 arguments are not supported,
* as the first argument will be the object whose method we are calling.
* <p>
* R is the return type.
* <p>
* For example, the type : .
* (String->int)
* Will be of type :
* Function<String, Void, Void..., Integer )
* <p>
* Primitive types ARE allowed in A1..9 and R. This is kind of illegal, but type erasure
* means that the generic type won't be in the compiled code ;-)
* <p>
* NOTE, This is not in package uk.co.nickthecoder.feather.runtime, because that package
* is safe to include in Feather's sandbox, whereas this classes may not be safe.
*/
public class Function<R, A1, A2, A3, A4, A5, A6, A7, A8, A9> {
private final Method method;
private final Object[] curriedArgs;
/**
* Called by FunctionStackEntry, which also looks up the [Method] in hard-coded bytecode.
*/
public Function(Method method) {
this.method = method;
curriedArgs = new Object[0];
}
/**
* Called by [curry], from an existing [Function] instance, so we already know the [Method].
*/
private Function(Method method, Object[] curriedArgs) {
this.method = method;
this.curriedArgs = curriedArgs;
}
/**
* Invokes (calls) the function.
* If this is a curried function, then the [Method] will have more parameters than
* the length of [args].
* <p>
* If [method] is not static, then the first argument (from the join of curriedArgs and arg)
* is the receiver, and the remainder are the method's arguments.
*/
public R invoke(Object... args) throws InvocationTargetException, IllegalAccessException {
if (Modifier.isStatic(method.getModifiers())) {
int parameterCount = method.getParameterCount();
int expectedArgCount = parameterCount - curriedArgs.length;
if (expectedArgCount != args.length) {
throw new IllegalArgumentException("Expected " + expectedArgCount + " but found " + args.length);
}
Object[] allArgs;
if (curriedArgs.length == 0) {
allArgs = args;
} else {
allArgs = Arrays.copyOf(curriedArgs, parameterCount);
// Copy args into allArgs after the curried args.
System.arraycopy(args, 0, allArgs, curriedArgs.length, args.length);
}
@SuppressWarnings("unchecked")
R result = (R) method.invoke(null, allArgs);
return result;
} else {
int parameterCount = method.getParameterCount();
int expectedArgCount = parameterCount + 1 - curriedArgs.length;
if (expectedArgCount != args.length) {
throw new IllegalArgumentException("Expected " + expectedArgCount + " arguments but found " + args.length);
}
Object[] allArgs; // Not including the receiver
Object receiver;
if (curriedArgs.length == 1) {
receiver = curriedArgs[0];
allArgs = args;
} else if (curriedArgs.length == 0) {
receiver = args[0];
allArgs = new Object[method.getParameterCount()];
System.arraycopy(args, 1, allArgs, 0, args.length - 1);
} else {
receiver = curriedArgs[0];
allArgs = new Object[method.getParameterCount()];
System.arraycopy(curriedArgs, 1, allArgs, 0, curriedArgs.length - 1);
System.arraycopy(args, 0, allArgs, curriedArgs.length - 1, args.length);
}
@SuppressWarnings("unchecked")
R result = (R) method.invoke(receiver, allArgs);
return result;
}
}
/**
* Curries the function. If you don't know what that means google "curried functions" ;-)
*/
public Function<R, A2, A3, A4, A5, A6, A7, A8, A9, Void> curry(A1 arg) {
int newCurryLength = curriedArgs.length + 1;
int requiredArgCount = method.getParameterCount();
if (!Modifier.isStatic(method.getModifiers())) {
++requiredArgCount;
}
if (requiredArgCount < newCurryLength) {
throw new IllegalStateException("Attempted to curry a function with no parameters");
}
Object[] newCurriedArgs = Arrays.copyOf(curriedArgs, newCurryLength);
newCurriedArgs[newCurryLength - 1] = arg;
return new Function<R, A2, A3, A4, A5, A6, A7, A8, A9, Void>(method, newCurriedArgs);
}
/**
* Curry two arguments
*/
public Function<R, A3, A4, A5, A6, A7, A8, A9, Void, Void> curry(A1 arg1, A2 arg2) {
return curry(arg1).curry(arg2);
}
/**
* Curry three arguments
*/
public Function<R, A4, A5, A6, A7, A8, A9, Void, Void, Void> curry(A1 arg1, A2 arg2, A3 arg3) {
return curry(arg1).curry(arg2).curry(arg3);
}
/**
* Curry four arguments
*/
public Function<R, A5, A6, A7, A8, A9, Void, Void, Void, Void> curry(A1 arg1, A2 arg2, A3 arg3, A4 arg4) {
return curry(arg1).curry(arg2).curry(arg3).curry(arg4);
}
/**
* Curry five arguments
*/
public Function<R, A6, A7, A8, A9, Void, Void, Void, Void, Void> curry(A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5) {
return curry(arg1).curry(arg2).curry(arg3).curry(arg4).curry(arg5);
}
}