in src/main/java/org/apache/commons/jexl3/internal/Interpreter.java [243:398]
protected Object call(final JexlNode node, final Object target, final Object funcNode, final ASTArguments argNode) {
cancelCheck(node);
// evaluate the arguments
final Object[] argv = visit(argNode, null);
final String methodName;
boolean cacheable = cache;
boolean isavar = false;
Object functor = funcNode;
// get the method name if identifier
if (functor instanceof ASTIdentifier) {
// function call, target is context or namespace (if there was one)
final ASTIdentifier methodIdentifier = (ASTIdentifier) functor;
final int symbol = methodIdentifier.getSymbol();
methodName = methodIdentifier.getName();
functor = null;
// is it a global or local variable ?
if (target == context) {
if (frame != null && frame.has(symbol)) {
functor = frame.get(symbol);
isavar = functor != null;
} else if (context.has(methodName)) {
functor = context.get(methodName);
isavar = functor != null;
}
// name is a variable, can't be cached
cacheable &= !isavar;
}
} else if (functor instanceof ASTIdentifierAccess) {
// a method call on target
methodName = ((ASTIdentifierAccess) functor).getName();
functor = null;
cacheable = true;
} else if (functor != null) {
// ...(x)(y)
methodName = null;
cacheable = false;
} else if (!node.isSafeLhs(isSafe())) {
return unsolvableMethod(node, "?(...)");
} else {
// safe lhs
return null;
}
// solving the call site
final CallDispatcher call = new CallDispatcher(node, cacheable);
try {
// do we have a cached version method/function name ?
final Object eval = call.tryEval(target, methodName, argv);
if (JexlEngine.TRY_FAILED != eval) {
return eval;
}
boolean functorp = false;
boolean narrow = false;
// pseudo loop to try acquiring methods without and with argument narrowing
while (true) {
call.narrow = narrow;
// direct function or method call
if (functor == null || functorp) {
// try a method or function from context
if (call.isTargetMethod(target, methodName, argv)) {
return call.eval(methodName);
}
if (target == context) {
// solve 'null' namespace
final Object namespace = resolveNamespace(null, node);
if (namespace != null
&& namespace != context
&& call.isTargetMethod(namespace, methodName, argv)) {
return call.eval(methodName);
}
// do not try context function since this was attempted
// 10 lines above...; solve as an arithmetic function
if (call.isArithmeticMethod(methodName, argv)) {
return call.eval(methodName);
}
// could not find a method, try as a property of a non-context target (performed once)
} else {
// try prepending target to arguments and look for
// applicable method in context...
final Object[] pargv = functionArguments(target, narrow, argv);
if (call.isContextMethod(methodName, pargv)) {
return call.eval(methodName);
}
// ...or arithmetic
if (call.isArithmeticMethod(methodName, pargv)) {
return call.eval(methodName);
}
// the method may also be a functor stored in a property of the target
if (!narrow) {
final JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
if (get != null) {
functor = get.tryInvoke(target, methodName);
functorp = functor != null;
}
}
}
}
// this may happen without the above when we are chaining call like x(a)(b)
// or when a var/symbol or antish var is used as a "function" name
if (functor != null) {
// lambda, script or jexl method will do
if (functor instanceof JexlScript) {
return ((JexlScript) functor).execute(context, argv);
}
if (functor instanceof JexlMethod) {
return ((JexlMethod) functor).invoke(target, argv);
}
final String mCALL = "call";
// may be a generic callable, try a 'call' method
if (call.isTargetMethod(functor, mCALL, argv)) {
return call.eval(mCALL);
}
// functor is a var, may be method is a global one ?
if (isavar) {
if (call.isContextMethod(methodName, argv)) {
return call.eval(methodName);
}
if (call.isArithmeticMethod(methodName, argv)) {
return call.eval(methodName);
}
}
// try prepending functor to arguments and look for
// context or arithmetic function called 'call'
final Object[] pargv = functionArguments(functor, narrow, argv);
if (call.isContextMethod(mCALL, pargv)) {
return call.eval(mCALL);
}
if (call.isArithmeticMethod(mCALL, pargv)) {
return call.eval(mCALL);
}
}
// if we did not find an exact method by name and we haven't tried yet,
// attempt to narrow the parameters and if this succeeds, try again in next loop
if (narrow || !arithmetic.narrowArguments(argv)) {
break;
}
narrow = true;
// continue;
}
}
catch (final JexlException.TryFailed xany) {
throw invocationException(node, methodName, xany);
}
catch (final JexlException xthru) {
if (xthru.getInfo() != null) {
throw xthru;
}
}
catch (final Exception xany) {
throw invocationException(node, methodName, xany);
}
// we have either evaluated and returned or no method was found
return node.isSafeLhs(isSafe())
? null
: unsolvableMethod(node, methodName, argv);
}