use of org.apache.commons.jexl3.introspection.JexlMethod in project commons-jexl by apache.
the class Operators method tryAssignOverload.
/**
* Evaluates an assign operator.
* <p>
* This takes care of finding and caching the operator method when appropriate.
* If an overloads returns Operator.ASSIGN, it means the side-effect is complete.
* Otherwise, a += b <=> a = a + b
* </p>
* @param node the syntactic node
* @param operator the operator
* @param args the arguments, the first one being the target of assignment
* @return JexlOperator.ASSIGN if operation assignment has been performed,
* JexlEngine.TRY_FAILED if no operation was performed,
* the value to use as the side effect argument otherwise
*/
protected Object tryAssignOverload(final JexlNode node, final JexlOperator operator, final Object... args) {
final JexlArithmetic arithmetic = interpreter.arithmetic;
if (args.length != operator.getArity()) {
return JexlEngine.TRY_FAILED;
}
// try to call overload with side effect
Object result = tryOverload(node, operator, args);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
// call base operator
final JexlOperator base = operator.getBaseOperator();
if (base == null) {
throw new IllegalArgumentException("must be called with a side-effect operator");
}
if (operators != null && operators.overloads(base)) {
// in case there is an overload on the base operator
try {
final JexlMethod vm = operators.getOperator(base, args);
if (vm != null) {
result = vm.invoke(arithmetic, args);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
}
} catch (final Exception xany) {
interpreter.operatorError(node, base, xany);
}
}
// base eval
try {
switch(operator) {
case SELF_ADD:
return arithmetic.add(args[0], args[1]);
case SELF_SUBTRACT:
return arithmetic.subtract(args[0], args[1]);
case SELF_MULTIPLY:
return arithmetic.multiply(args[0], args[1]);
case SELF_DIVIDE:
return arithmetic.divide(args[0], args[1]);
case SELF_MOD:
return arithmetic.mod(args[0], args[1]);
case SELF_AND:
return arithmetic.and(args[0], args[1]);
case SELF_OR:
return arithmetic.or(args[0], args[1]);
case SELF_XOR:
return arithmetic.xor(args[0], args[1]);
default:
// unexpected, new operator added?
throw new UnsupportedOperationException(operator.getOperatorSymbol());
}
} catch (final Exception xany) {
interpreter.operatorError(node, base, xany);
}
return JexlEngine.TRY_FAILED;
}
use of org.apache.commons.jexl3.introspection.JexlMethod in project commons-jexl by apache.
the class ReferenceUberspect method discoverFind.
/**
* Discovers a an optional getter.
* <p>The method to be found should be named "{find}{P,p}property and return an Optional<?>.</p>
*
* @param is the uberspector
* @param clazz the class to find the get method from
* @param property the property name to find
* @return the executor if found, null otherwise
*/
private static JexlPropertyGet discoverFind(final JexlUberspect is, final Class<?> clazz, final String property) {
if (property == null || property.isEmpty()) {
return null;
}
// this is gross and linear, but it keeps it straightforward.
JexlMethod method;
// "find".length() == 4
final int start = 4;
// start with get<Property>
final StringBuilder sb = new StringBuilder("find");
sb.append(property);
// uppercase nth char
final char c = sb.charAt(start);
sb.setCharAt(start, Character.toUpperCase(c));
method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
// lowercase nth char
if (method == null) {
sb.setCharAt(start, Character.toLowerCase(c));
method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
}
if (method != null && Optional.class.equals(method.getReturnType())) {
final JexlMethod getter = method;
final String name = sb.toString();
return new JexlPropertyGet() {
@Override
public Object invoke(Object obj) throws Exception {
return getter.invoke(obj);
}
@Override
public Object tryInvoke(Object obj, Object key) throws JexlException.TryFailed {
return !Objects.equals(property, key) ? JexlEngine.TRY_FAILED : getter.tryInvoke(name, obj);
}
@Override
public boolean tryFailed(Object rval) {
return rval == JexlEngine.TRY_FAILED;
}
@Override
public boolean isCacheable() {
return getter.isCacheable();
}
};
}
return null;
}
use of org.apache.commons.jexl3.introspection.JexlMethod in project commons-jexl by apache.
the class MethodTest method testTryFailed.
@Test
public void testTryFailed() throws Exception {
// JEXL-257
final Functor func = new Functor();
final JexlContext ctxt = new MapContext();
ctxt.set("func", func);
Object result;
final JexlUberspect uber = JEXL.getUberspect();
// tryInvoke
final JexlMethod method = uber.getMethod(func, "over", "foo", 42);
Assert.assertNotNull(method);
// tryInvoke succeeds
result = method.tryInvoke("over", func, "foo", 42);
Assert.assertEquals("foo + 42", result);
// tryInvoke fails
func.setKill(true);
try {
/*result = */
method.tryInvoke("over", func, "foo", 42);
Assert.fail("should throw TryFailed");
} catch (final JexlException.TryFailed xfail) {
Assert.assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
}
func.setKill(false);
final JexlPropertySet setter = uber.getPropertySet(func, "under", "42");
result = setter.tryInvoke(func, "under", "42");
Assert.assertFalse(setter.tryFailed(result));
Assert.assertEquals("42", result);
final JexlPropertyGet getter = uber.getPropertyGet(func, "under");
result = getter.tryInvoke(func, "under");
Assert.assertFalse(getter.tryFailed(result));
Assert.assertEquals("42", result);
func.setKill(true);
try {
/*result = */
setter.tryInvoke(func, "under", "42");
Assert.fail("should throw TryFailed");
} catch (final JexlException.TryFailed xfail) {
Assert.assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
}
func.setKill(false);
result = setter.tryInvoke(func, "under", "-42");
Assert.assertEquals("-42", result);
func.setKill(true);
try {
/*result = */
getter.tryInvoke(func, "under");
Assert.fail("should throw TryFailed");
} catch (final JexlException.TryFailed xfail) {
Assert.assertEquals(UnsupportedOperationException.class, xfail.getCause().getClass());
}
func.setKill(false);
result = getter.tryInvoke(func, "under");
Assert.assertFalse(getter.tryFailed(result));
Assert.assertEquals("-42", result);
}
use of org.apache.commons.jexl3.introspection.JexlMethod in project commons-jexl by apache.
the class Interpreter method call.
/**
* Calls a method (or function).
* <p>
* Method resolution is a follows:
* 1 - attempt to find a method in the target passed as parameter;
* 2 - if this fails, seeks a JexlScript or JexlMethod or a duck-callable* as a property of that target;
* 3 - if this fails, narrow the arguments and try again 1
* 4 - if this fails, seeks a context or arithmetic method with the proper name taking the target as first argument;
* </p>
* *duck-callable: an object where a "call" function exists
*
* @param node the method node
* @param target the target of the method, what it should be invoked upon
* @param funcNode the object carrying the method or function or the method identifier
* @param argNode the node carrying the arguments
* @return the result of the method invocation
*/
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);
}
// 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;
}
}
}
}
// 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 && target == context) {
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);
}
}
// attempt to narrow the parameters and if this succeeds, try again in next loop
if (narrow || !arithmetic.narrowArguments(argv)) {
break;
}
narrow = true;
// continue;
}
} catch (JexlException.Method xmethod) {
// ignore and handle at end; treat as an inner discover that fails
} catch (final JexlException.TryFailed xany) {
throw invocationException(node, methodName, xany);
} catch (final JexlException xthru) {
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);
}
use of org.apache.commons.jexl3.introspection.JexlMethod in project commons-jexl by apache.
the class Interpreter method visit.
@Override
protected Object visit(final ASTUnaryMinusNode node, final Object data) {
// use cached value if literal
final Object value = node.jjtGetValue();
if (value != null && !(value instanceof JexlMethod)) {
return value;
}
final JexlNode valNode = node.jjtGetChild(0);
final Object val = valNode.jjtAccept(this, data);
try {
final Object result = operators.tryOverload(node, JexlOperator.NEGATE, val);
if (result != JexlEngine.TRY_FAILED) {
return result;
}
Object number = arithmetic.negate(val);
// cache if number literal and negate is idempotent
if (number instanceof Number && valNode instanceof ASTNumberLiteral) {
number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
if (arithmetic.isNegateStable()) {
node.jjtSetValue(number);
}
}
return number;
} catch (final ArithmeticException xrt) {
throw new JexlException(valNode, "- error", xrt);
}
}
Aggregations