View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Objects;
23  import java.util.Queue;
24  import java.util.concurrent.atomic.AtomicBoolean;
25  
26  import org.apache.commons.jexl3.JexlArithmetic;
27  import org.apache.commons.jexl3.JexlContext;
28  import org.apache.commons.jexl3.JexlContext.NamespaceFunctor;
29  import org.apache.commons.jexl3.JexlEngine;
30  import org.apache.commons.jexl3.JexlException;
31  import org.apache.commons.jexl3.JexlException.VariableIssue;
32  import org.apache.commons.jexl3.JexlOperator;
33  import org.apache.commons.jexl3.JexlOptions;
34  import org.apache.commons.jexl3.introspection.JexlMethod;
35  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
36  import org.apache.commons.jexl3.introspection.JexlPropertySet;
37  import org.apache.commons.jexl3.introspection.JexlUberspect;
38  import org.apache.commons.jexl3.parser.ASTArrayAccess;
39  import org.apache.commons.jexl3.parser.ASTAssignment;
40  import org.apache.commons.jexl3.parser.ASTFunctionNode;
41  import org.apache.commons.jexl3.parser.ASTIdentifier;
42  import org.apache.commons.jexl3.parser.ASTIdentifierAccess;
43  import org.apache.commons.jexl3.parser.ASTMethodNode;
44  import org.apache.commons.jexl3.parser.ASTNullpNode;
45  import org.apache.commons.jexl3.parser.ASTReference;
46  import org.apache.commons.jexl3.parser.ASTTernaryNode;
47  import org.apache.commons.jexl3.parser.ASTVar;
48  import org.apache.commons.jexl3.parser.JexlNode;
49  import org.apache.commons.jexl3.parser.ParserVisitor;
50  import org.apache.commons.logging.Log;
51  
52  /**
53   * The helper base of an interpreter of JEXL syntax.
54   *
55   * @since 3.0
56   */
57  public abstract class InterpreterBase extends ParserVisitor {
58  
59      /**
60       * Helping dispatch function calls.
61       */
62      protected class CallDispatcher {
63  
64          /** The syntactic node. */
65          final JexlNode node;
66  
67          /** Whether solution is cacheable. */
68          final boolean cacheable;
69  
70          /** Whether arguments have been narrowed.  */
71          boolean narrow;
72  
73          /** The method to call. */
74          JexlMethod vm;
75  
76          /** The method invocation target. */
77          Object target;
78  
79          /** The actual arguments. */
80          Object[] argv;
81  
82          /** The cacheable funcall if any. */
83          Funcall funcall;
84  
85          /**
86           * Dispatcher ctor.
87           *
88           * @param anode the syntactic node.
89           * @param acacheable whether resolution can be cached
90           */
91          CallDispatcher(final JexlNode anode, final boolean acacheable) {
92              this.node = anode;
93              this.cacheable = acacheable;
94          }
95  
96          /**
97           * Evaluates the method previously dispatched.
98           *
99           * @param methodName the method name
100          * @return the method invocation result
101          * @throws Exception when invocation fails
102          */
103         protected Object eval(final String methodName) throws Exception {
104             // we have either evaluated and returned or might have found a method
105             if (vm != null) {
106                 // vm cannot be null if xjexl is null
107                 final Object eval = vm.invoke(target, argv);
108                 // cache executor in volatile JexlNode.value
109                 if (funcall != null) {
110                     node.jjtSetValue(funcall);
111                 }
112                 return eval;
113             }
114             return unsolvableMethod(node, methodName, argv);
115         }
116 
117         /**
118          * Whether the method is an arithmetic method.
119          *
120          * @param methodName the method name
121          * @param arguments the method arguments
122          * @return true if arithmetic, false otherwise
123          */
124         protected boolean isArithmeticMethod(final String methodName, final Object[] arguments) {
125             vm = uberspect.getMethod(arithmetic, methodName, arguments);
126             if (vm != null) {
127                 argv = arguments;
128                 target = arithmetic;
129                 if (cacheable && vm.isCacheable()) {
130                     funcall = new ArithmeticFuncall(vm, narrow);
131                 }
132                 return true;
133             }
134             return false;
135         }
136 
137         /**
138          * Whether the method is a context method.
139          *
140          * @param methodName the method name
141          * @param arguments the method arguments
142          * @return true if arithmetic, false otherwise
143          */
144         protected boolean isContextMethod(final String methodName, final Object[] arguments) {
145             vm = uberspect.getMethod(context, methodName, arguments);
146             if (vm != null) {
147                 argv = arguments;
148                 target = context;
149                 if (cacheable && vm.isCacheable()) {
150                     funcall = new ContextFuncall(vm, narrow);
151                 }
152                 return true;
153             }
154             return false;
155         }
156 
157         /**
158          * Whether the method is a target method.
159          *
160          * @param ntarget the target instance
161          * @param methodName the method name
162          * @param arguments the method arguments
163          * @return true if arithmetic, false otherwise
164          */
165         protected boolean isTargetMethod(final Object ntarget, final String methodName, final Object[] arguments) {
166             // try a method
167             vm = uberspect.getMethod(ntarget, methodName, arguments);
168             if (vm != null) {
169                 argv = arguments;
170                 target = ntarget;
171                 if (cacheable && vm.isCacheable()) {
172                     funcall = new Funcall(vm, narrow);
173                 }
174                 return true;
175             }
176             return false;
177         }
178 
179         /**
180          * Attempt to reuse last funcall cached in volatile JexlNode.value (if
181          * it was cacheable).
182          *
183          * @param ntarget the target instance
184          * @param methodName the method name
185          * @param arguments the method arguments
186          * @return TRY_FAILED if invocation was not possible or failed, the
187          * result otherwise
188          */
189         protected Object tryEval(final Object ntarget, final String methodName, final Object[] arguments) {
190             // do we have  a method/function name ?
191             // attempt to reuse last funcall cached in volatile JexlNode.value (if it was not a variable)
192             if (methodName != null && cacheable && ntarget != null) {
193                 final Object cached = node.jjtGetValue();
194                 if (cached instanceof Funcall) {
195                     return ((Funcall) cached).tryInvoke(InterpreterBase.this, methodName, ntarget, arguments);
196                 }
197             }
198             return JexlEngine.TRY_FAILED;
199         }
200     }
201 
202     /**
203      * Cached arithmetic function call.
204      */
205     protected static class ArithmeticFuncall extends Funcall {
206 
207         /**
208          * Constructs a new instance.
209          *
210          * @param jme  the method
211          * @param flag the narrow flag
212          */
213         protected ArithmeticFuncall(final JexlMethod jme, final boolean flag) {
214             super(jme, flag);
215         }
216 
217         @Override
218         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
219             return me.tryInvoke(name, ii.arithmetic, ii.functionArguments(target, narrow, args));
220         }
221     }
222 
223     /**
224      * Cached context function call.
225      */
226     protected static class ContextFuncall extends Funcall {
227 
228         /**
229          * Constructs a new instance.
230          *
231          * @param jme  the method
232          * @param flag the narrow flag
233          */
234         protected ContextFuncall(final JexlMethod jme, final boolean flag) {
235             super(jme, flag);
236         }
237 
238         @Override
239         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
240             return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
241         }
242     }
243 
244     /**
245      * A ctor that needs a context as 1st argument.
246      */
247     protected static class ContextualCtor extends Funcall {
248 
249         /**
250          * Constructs a new instance.
251          *
252          * @param jme the method
253          * @param flag the narrow flag
254          */
255         protected ContextualCtor(final JexlMethod jme, final boolean flag) {
256             super(jme, flag);
257         }
258 
259         @Override
260         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
261             return me.tryInvoke(name, target, ii.callArguments(ii.context, narrow, args));
262         }
263     }
264 
265     /**
266      * Cached function call.
267      */
268     protected static class Funcall implements JexlNode.Funcall {
269 
270         /** Whether narrow should be applied to arguments. */
271         protected final boolean narrow;
272 
273         /** The JexlMethod to delegate the call to. */
274         protected final JexlMethod me;
275 
276         /**
277          * Constructs a new instance.
278          *
279          * @param jme  the method
280          * @param flag the narrow flag
281          */
282         protected Funcall(final JexlMethod jme, final boolean flag) {
283             this.me = jme;
284             this.narrow = flag;
285         }
286 
287         /**
288          * Try invocation.
289          *
290          * @param ii     the interpreter
291          * @param name   the method name
292          * @param target the method target
293          * @param args   the method arguments
294          * @return the method invocation result (or JexlEngine.TRY_FAILED)
295          */
296         protected Object tryInvoke(final InterpreterBase ii, final String name, final Object target, final Object[] args) {
297             return me.tryInvoke(name, target, ii.functionArguments(null, narrow, args));
298         }
299     }
300 
301     /** Empty parameters for method matching. */
302     protected static final Object[] EMPTY_PARAMS = {};
303 
304     /**
305      * Pretty-prints a failing property value (de)reference.
306      * <p>Used by calls to unsolvableProperty(...).</p>
307      *
308      * @param node the property node
309      * @return the (pretty) string value
310      */
311     protected static String stringifyPropertyValue(final JexlNode node) {
312         return node != null ? new Debugger().depth(1).data(node) : "???";
313     }
314 
315     /** The JEXL engine. */
316     protected final Engine jexl;
317 
318     /** The logger. */
319     protected final Log logger;
320 
321     /** The uberspect. */
322     protected final JexlUberspect uberspect;
323 
324     /** The arithmetic handler. */
325     protected final JexlArithmetic arithmetic;
326 
327     /** The context to store/retrieve variables. */
328     protected final JexlContext context;
329 
330     /** The options. */
331     protected final JexlOptions options;
332 
333     /** Cache executors. */
334     protected final boolean cache;
335 
336     /** Cancellation support. */
337     protected final AtomicBoolean cancelled;
338 
339     /** The namespace resolver. */
340     protected final JexlContext.NamespaceResolver ns;
341 
342     /** The class name resolver. */
343     protected final JexlUberspect.ClassNameResolver fqcnSolver;
344 
345     /** The operators evaluation delegate. */
346     protected final JexlOperator.Uberspect operators;
347 
348     /** The map of 'prefix:function' to object resolving as namespaces. */
349     protected final Map<String, Object> functions;
350 
351     /** The map of dynamically created namespaces, NamespaceFunctor or duck-types of those. */
352     protected Map<String, Object> functors;
353 
354     /**
355      * Creates an interpreter base.
356      *
357      * @param engine   the engine creating this interpreter
358      * @param opts     the evaluation options
359      * @param aContext the evaluation context
360      */
361     protected InterpreterBase(final Engine engine, final JexlOptions opts, final JexlContext aContext) {
362         this.jexl = engine;
363         this.logger = jexl.logger;
364         this.uberspect = jexl.uberspect;
365         this.context = aContext != null ? aContext : JexlEngine.EMPTY_CONTEXT;
366         this.cache = engine.cache != null;
367         final JexlArithmetic jexla = jexl.arithmetic;
368         this.options = opts == null ? engine.evalOptions(aContext) : opts;
369         this.arithmetic = jexla.options(options);
370         if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass()) && logger.isWarnEnabled()) {
371             logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
372                     + ", got " + arithmetic.getClass().getSimpleName()
373             );
374         }
375         if (this.context instanceof JexlContext.NamespaceResolver) {
376             ns = (JexlContext.NamespaceResolver) context;
377         } else {
378             ns = JexlEngine.EMPTY_NS;
379         }
380         AtomicBoolean acancel = null;
381         if (this.context instanceof JexlContext.CancellationHandle) {
382             acancel = ((JexlContext.CancellationHandle) context).getCancellation();
383         }
384         this.cancelled = acancel != null ? acancel : new AtomicBoolean();
385         this.functions = options.getNamespaces();
386         this.functors = null;
387         JexlOperator.Uberspect ops = uberspect.getOperator(arithmetic);
388         if (ops == null) {
389             ops = new Operator(uberspect, arithmetic);
390         }
391         this.operators = ops;
392         // the import package facility
393         this.fqcnSolver = engine.createConstantResolver(options.getImports());
394     }
395 
396     /**
397      * Copy constructor.
398      *
399      * @param ii the base to copy
400      * @param jexla the arithmetic instance to use (or null)
401      */
402     protected InterpreterBase(final InterpreterBase ii, final JexlArithmetic jexla) {
403         jexl = ii.jexl;
404         logger = ii.logger;
405         uberspect = ii.uberspect;
406         arithmetic = jexla;
407         context = ii.context;
408         options = ii.options.copy();
409         cache = ii.cache;
410         ns = ii.ns;
411         operators = ii.operators;
412         cancelled = ii.cancelled;
413         functions = ii.functions;
414         functors = ii.functors;
415         fqcnSolver = ii.fqcnSolver;
416     }
417 
418     /**
419      * Triggered when an annotation processing fails.
420      *
421      * @param node     the node where the error originated from
422      * @param annotation the annotation name
423      * @param cause    the cause of error (if any)
424      * @return throws a JexlException if strict and not silent, null otherwise
425      */
426     protected Object annotationError(final JexlNode node, final String annotation, final Throwable cause) {
427         if (isStrictEngine()) {
428             throw new JexlException.Annotation(node, annotation, cause);
429         }
430         if (logger.isDebugEnabled()) {
431             logger.debug(JexlException.annotationError(node, annotation), cause);
432         }
433         return null;
434     }
435 
436     /**
437      * Concatenate arguments in call(...).
438      *
439      * @param target the pseudo-method owner, first to-be argument
440      * @param narrow whether we should attempt to narrow number arguments
441      * @param args   the other (non-null) arguments
442      * @return the arguments array
443      */
444     protected Object[] callArguments(final Object target, final boolean narrow, final Object[] args) {
445         // makes target 1st args, copy others - optionally narrow numbers
446         final Object[] nargv = new Object[args.length + 1];
447         if (narrow) {
448             nargv[0] = functionArgument(true, target);
449             for (int a = 1; a <= args.length; ++a) {
450                 nargv[a] = functionArgument(true, args[a - 1]);
451             }
452         } else {
453             nargv[0] = target;
454             System.arraycopy(args, 0, nargv, 1, args.length);
455         }
456         return nargv;
457     }
458 
459     /**
460      * Cancels this evaluation, setting the cancel flag that will result in a JexlException.Cancel to be thrown.
461      *
462      * @return false if already cancelled, true otherwise
463      */
464     protected  boolean cancel() {
465         return cancelled.compareAndSet(false, true);
466     }
467 
468     /**
469      * Throws a JexlException.Cancel if script execution was cancelled.
470      *
471      * @param node the node being evaluated
472      */
473     protected void cancelCheck(final JexlNode node) {
474         if (isCancelled()) {
475             throw new JexlException.Cancel(node);
476         }
477     }
478 
479     /**
480      * Attempt to call close() if supported.
481      * <p>This is used when dealing with auto-closeable (duck-like) objects
482      *
483      * @param closeable the object we'd like to close
484      */
485     protected void closeIfSupported(final Object closeable) {
486         if (closeable != null) {
487             final JexlMethod mclose = uberspect.getMethod(closeable, "close", EMPTY_PARAMS);
488             if (mclose != null) {
489                 try {
490                     mclose.invoke(closeable, EMPTY_PARAMS);
491                 } catch (final Exception xignore) {
492                     logger.warn(xignore);
493                 }
494             }
495         }
496     }
497 
498     /**
499      * Attempt to call close() if supported.
500      * <p>This is used when dealing with auto-closeable (duck-like) objects
501      *
502      * @param closeables the object queue we'd like to close
503      */
504     protected void closeIfSupported(final Queue<Object> closeables) {
505         for(final Object closeable : closeables) {
506             closeIfSupported(closeable);
507         }
508     }
509 
510     /**
511      * Triggered when a captured variable is const and assignment is attempted.
512      *
513      * @param node  the node where the error originated from
514      * @param variable   the variable name
515      * @return throws JexlException if strict and not silent, null otherwise
516      */
517     protected Object constVariable(final JexlNode node, final String variable) {
518         return variableError(node, variable, VariableIssue.CONST);
519     }
520 
521     /**
522      * Defines a variable.
523      *
524      * @param variable the variable to define
525      * @param frame the frame in which it will be defined
526      * @return true if definition succeeded, false otherwise
527      */
528     protected boolean defineVariable(final ASTVar variable, final LexicalFrame frame) {
529         final int symbol = variable.getSymbol();
530         if (symbol < 0) {
531             return false;
532         }
533         if (variable.isRedefined()) {
534             return false;
535         }
536         return frame.defineSymbol(symbol, variable.isCaptured());
537     }
538 
539     /**
540      * Finds the node causing a NPE for diadic operators.
541      *
542      * @param node  the parent node
543      * @param left  the left argument
544      * @param right the right argument
545      * @return the left, right or parent node
546      */
547     protected JexlNode findNullOperand(final JexlNode node, final Object left, final Object right) {
548         if (left == null) {
549             return node.jjtGetChild(0);
550         }
551         if (right == null) {
552             return node.jjtGetChild(1);
553         }
554         return node;
555     }
556 
557     /**
558      * @deprecated
559      */
560     @Deprecated
561     protected JexlNode findNullOperand(final RuntimeException xrt, final JexlNode node, final Object left, final Object right) {
562         return findNullOperand(node, left, right);
563     }
564 
565     /**
566      * Optionally narrows an argument for a function call.
567      *
568      * @param narrow whether narrowing should occur
569      * @param arg    the argument
570      * @return the narrowed argument
571      */
572     protected Object functionArgument(final boolean narrow, final Object arg) {
573         return narrow && arg instanceof Number ? arithmetic.narrow((Number) arg) : arg;
574     }
575 
576     /**
577      * Concatenate arguments in call(...).
578      * <p>When target == context, we are dealing with a global namespace function call
579      *
580      * @param target the pseudo-method owner, first to-be argument
581      * @param narrow whether we should attempt to narrow number arguments
582      * @param args   the other (non-null) arguments
583      * @return the arguments array
584      */
585     protected Object[] functionArguments(final Object target, final boolean narrow, final Object[] args) {
586         // when target == context, we are dealing with the null namespace
587         if (target == null || target == context) {
588             if (narrow) {
589                 arithmetic.narrowArguments(args);
590             }
591             return args;
592         }
593         // makes target 1st args, copy others - optionally narrow numbers
594         final Object[] nargv = new Object[args.length + 1];
595         if (narrow) {
596             nargv[0] = functionArgument(true, target);
597             for (int a = 1; a <= args.length; ++a) {
598                 nargv[a] = functionArgument(true, args[a - 1]);
599             }
600         } else {
601             nargv[0] = target;
602             System.arraycopy(args, 0, nargv, 1, args.length);
603         }
604         return nargv;
605     }
606 
607     /**
608      * Gets an attribute of an object.
609      *
610      * @param object    to retrieve value from
611      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
612      * @param node      the node that evaluated as the object
613      * @return the attribute value
614      */
615     protected Object getAttribute(final Object object, final Object attribute, final JexlNode node) {
616         if (object == null) {
617             throw new JexlException(node, "object is null");
618         }
619         cancelCheck(node);
620         Exception xcause = null;
621         try {
622             final JexlOperator operator;
623             if (node != null) {
624                 // attempt to reuse last executor cached in volatile JexlNode.value
625                 if (cache) {
626                     final Object cached = node.jjtGetValue();
627                     if (cached instanceof JexlPropertyGet) {
628                         final JexlPropertyGet vg = (JexlPropertyGet) cached;
629                         final Object value = vg.tryInvoke(object, attribute);
630                         if (!vg.tryFailed(value)) {
631                             return value;
632                         }
633                     }
634                 }
635                 // try operator overload
636                 operator = node.jjtGetParent() instanceof ASTArrayAccess ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
637                 final Object result = operators.tryOverload(node, operator, object, attribute);
638                 if (result != JexlEngine.TRY_FAILED) {
639                     return result;
640                 }
641             } else {
642                 operator = JexlOperator.PROPERTY_GET;
643             }
644             // resolve that property
645             final List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
646             final JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
647             if (vg != null) {
648                 final Object value = vg.invoke(object);
649                 // cache executor in volatile JexlNode.value
650                 if (node != null && cache && vg.isCacheable()) {
651                     node.jjtSetValue(vg);
652                 }
653                 return value;
654             }
655         } catch (final Exception xany) {
656             xcause = xany;
657         }
658         // lets fail
659         if (node == null) {
660             // direct call
661             final String error = "unable to get object property"
662                     + ", class: " + object.getClass().getName()
663                     + ", property: " + attribute;
664             throw new UnsupportedOperationException(error, xcause);
665         }
666         final boolean safe = node instanceof ASTIdentifierAccess && ((ASTIdentifierAccess) node).isSafe();
667         if (safe) {
668             return null;
669         }
670         final String attrStr = Objects.toString(attribute, null);
671         return unsolvableProperty(node, attrStr, true, xcause);
672     }
673 
674     /**
675      * Gets a value of a defined local variable or from the context.
676      *
677      * @param frame the local frame
678      * @param block the lexical block if any
679      * @param identifier the variable node
680      * @return the value
681      */
682     protected Object getVariable(final Frame frame, final LexicalScope block, final ASTIdentifier identifier) {
683         final int symbol = identifier.getSymbol();
684         final String name = identifier.getName();
685         // if we have a symbol, we have a scope thus a frame
686         if ((options.isLexicalShade() || identifier.isLexical()) && identifier.isShaded()) {
687             return undefinedVariable(identifier, name);
688         }
689         // a local var ?
690         if (symbol >= 0 && frame.has(symbol)) {
691             final Object value = frame.get(symbol);
692             // not out of scope with no lexical shade ?
693             if (value != Scope.UNDEFINED) {
694                 // null operand of an arithmetic operator ?
695                 if (value == null && isStrictOperand(identifier)) {
696                     return unsolvableVariable(identifier, name, false); // defined but null
697                 }
698                 return value;
699             }
700         }
701         // consider global
702         final Object value = context.get(name);
703         // is it null ?
704         if (value == null) {
705             // is it defined ?
706             if (!context.has(name)) {
707                 // not defined, ignore in some cases...
708                 final boolean ignore = identifier.jjtGetParent() instanceof ASTReference
709                         || isSafe() && (symbol >= 0 || identifier.jjtGetParent() instanceof ASTAssignment);
710                 if (!ignore) {
711                     return undefinedVariable(identifier, name); // undefined
712                 }
713             } else if (isStrictOperand(identifier)) {
714                 return unsolvableVariable(identifier, name, false); // defined but null
715             }
716         }
717         return value;
718     }
719 
720     /**
721      * Triggered when method, function or constructor invocation fails with an exception.
722      *
723      * @param node       the node triggering the exception
724      * @param methodName the method/function name
725      * @param xany       the cause
726      * @return a JexlException that will be thrown
727      */
728     protected JexlException invocationException(final JexlNode node, final String methodName, final Throwable xany) {
729         final Throwable cause = xany.getCause();
730         if (cause instanceof JexlException) {
731             return (JexlException) cause;
732         }
733         if (cause instanceof InterruptedException) {
734             return new JexlException.Cancel(node);
735         }
736         return new JexlException(node, methodName, xany);
737     }
738 
739     /**
740      * @return true if interrupt throws a JexlException.Cancel.
741      */
742     protected boolean isCancellable() {
743         return options.isCancellable();
744     }
745 
746     /**
747      * Checks whether this interpreter execution was cancelled due to thread interruption.
748      *
749      * @return true if cancelled, false otherwise
750      */
751     protected boolean isCancelled() {
752         return cancelled.get() || Thread.currentThread().isInterrupted();
753     }
754 
755     /**
756      * Whether this interpreter ignores null in navigation expression as errors.
757      *
758      * @return true if safe, false otherwise
759      */
760     protected boolean isSafe() {
761         return options.isSafe();
762     }
763 
764     /**
765      * Whether this interpreter is currently evaluating with a silent mode.
766      *
767      * @return true if silent, false otherwise
768      */
769     protected boolean isSilent() {
770         return options.isSilent();
771     }
772 
773     /**
774      * Whether this interpreter is currently evaluating with a strict engine flag.
775      *
776      * @return true if strict engine, false otherwise
777      */
778     protected boolean isStrictEngine() {
779         return options.isStrict();
780     }
781 
782     /**
783      * @param node the operand node
784      * @return true if this node is an operand of a strict operator, false otherwise
785      */
786     protected boolean isStrictOperand(final JexlNode node) {
787        return node.jjtGetParent().isStrictOperator(arithmetic);
788     }
789 
790     /**
791      * Check if a null evaluated expression is protected by a ternary expression.
792      * <p>
793      * The rationale is that the ternary / elvis expressions are meant for the user to explicitly take control
794      * over the error generation; ie, ternaries can return null even if the engine in strict mode
795      * would normally throw an exception.
796      * </p>
797      *
798      * @return true if nullable variable, false otherwise
799      */
800     protected boolean isTernaryProtected(final JexlNode startNode) {
801         JexlNode node = startNode;
802         for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
803             // protect only the condition part of the ternary
804             if (walk instanceof ASTTernaryNode
805                     || walk instanceof ASTNullpNode) {
806                 return node == walk.jjtGetChild(0);
807             }
808             if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
809                 break;
810             }
811             node = walk;
812         }
813         return false;
814     }
815 
816     /**
817      * Checks whether a variable is defined.
818      * <p>The var may be either a local variable declared in the frame and
819      * visible from the block or defined in the context.
820      *
821      * @param frame the frame
822      * @param block the block
823      * @param name the variable name
824      * @return true if variable is defined, false otherwise
825      */
826     protected boolean isVariableDefined(final Frame frame, final LexicalScope block, final String name) {
827         if (frame != null && block != null) {
828             final Integer ref = frame.getScope().getSymbol(name);
829             final int symbol = ref != null ? ref : -1;
830             if (symbol >= 0  && block.hasSymbol(symbol)) {
831                 final Object value = frame.get(symbol);
832                 return value != Scope.UNDEFINED && value != Scope.UNDECLARED;
833             }
834         }
835         return context.has(name);
836     }
837 
838     /**
839      * Triggered when an operator fails.
840      *
841      * @param node     the node where the error originated from
842      * @param operator the operator symbol
843      * @param cause    the cause of error (if any)
844      * @return throws JexlException if strict and not silent, null otherwise
845      */
846     protected Object operatorError(final JexlNode node, final JexlOperator operator, final Throwable cause) {
847         if (isStrictEngine()) {
848             throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
849         }
850         if (logger.isDebugEnabled()) {
851             logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
852         }
853         return null;
854     }
855 
856     /**
857      * Triggered when a variable is lexically known as being redefined.
858      *
859      * @param node  the node where the error originated from
860      * @param variable   the variable name
861      * @return throws JexlException if strict and not silent, null otherwise
862      */
863     protected Object redefinedVariable(final JexlNode node, final String variable) {
864         return variableError(node, variable, VariableIssue.REDEFINED);
865     }
866 
867     /**
868      * Resolves a namespace, eventually allocating an instance using context as constructor argument.
869      * <p>
870      * The lifetime of such instances span the current expression or script evaluation.</p>
871      *
872      * @param prefix the prefix name (can be null for global namespace)
873      * @param node   the AST node
874      * @return the namespace instance
875      */
876     protected Object resolveNamespace(final String prefix, final JexlNode node) {
877         Object namespace;
878         // check whether this namespace is a functor
879         synchronized (this) {
880             if (functors != null) {
881                 namespace = functors.get(prefix);
882                 if (namespace != null) {
883                     return namespace;
884                 }
885             }
886         }
887         // check if namespace is a resolver
888         namespace = ns.resolveNamespace(prefix);
889         if (namespace == null) {
890             namespace = functions.get(prefix);
891             if (namespace == null) {
892                 namespace = jexl.getNamespace(prefix);
893             }
894             if (prefix != null && namespace == null) {
895                 throw new JexlException(node, "no such function namespace " + prefix, null);
896             }
897         }
898         Object functor = null;
899         // class or string (*1)
900         if (namespace instanceof Class<?> || namespace instanceof String) {
901             // the namespace(d) identifier
902             final ASTIdentifier nsNode = (ASTIdentifier) node.jjtGetChild(0);
903             final boolean cacheable = cache && prefix != null;
904             final Object cached = cacheable ? nsNode.jjtGetValue() : null;
905             // we know the class is used as namespace of static methods, no functor
906             if (cached instanceof Class<?>) {
907                 return cached;
908             }
909             // attempt to reuse last cached constructor
910             if (cached instanceof JexlContext.NamespaceFunctor) {
911                 final Object eval = ((JexlContext.NamespaceFunctor) cached).createFunctor(context);
912                 if (JexlEngine.TRY_FAILED != eval) {
913                     functor = eval;
914                     namespace = cached;
915                 }
916             }
917             if (functor == null) {
918                 // find a constructor with that context as argument or without
919                 for (int tried = 0; tried < 2; ++tried) {
920                     final boolean withContext = tried == 0;
921                     final JexlMethod ctor = withContext
922                             ? uberspect.getConstructor(namespace, context)
923                             : uberspect.getConstructor(namespace);
924                     if (ctor != null) {
925                         try {
926                             functor = withContext
927                                     ? ctor.invoke(namespace, context)
928                                     : ctor.invoke(namespace);
929                             // defensive
930                             if (functor != null) {
931                                 // wrap the namespace in a NamespaceFunctor to shield us from the actual
932                                 // number of arguments to call it with.
933                                 final Object nsFinal = namespace;
934                                 // make it a class (not a lambda!) so instanceof (see *2) will catch it
935                                 namespace = (NamespaceFunctor) context -> withContext
936                                         ? ctor.tryInvoke(null, nsFinal, context)
937                                         : ctor.tryInvoke(null, nsFinal);
938                                 if (cacheable && ctor.isCacheable()) {
939                                     nsNode.jjtSetValue(namespace);
940                                 }
941                                 break; // we found a constructor that did create a functor
942                             }
943                         } catch (final Exception xinst) {
944                             throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
945                         }
946                     }
947                 }
948                 // did not, will not create a functor instance; use a class, namespace of static methods
949                 if (functor == null) {
950                     try {
951                         // try to find a class with that name
952                         if (namespace instanceof String) {
953                             namespace = uberspect.getClassLoader().loadClass((String) namespace);
954                         }
955                         // we know it's a class in all cases (see *1)
956                         if (cacheable) {
957                             nsNode.jjtSetValue(namespace);
958                         }
959                     } catch (final ClassNotFoundException e) {
960                         // not a class
961                         throw new JexlException(node, "no such class namespace " + prefix, e);
962                     }
963                 }
964             }
965         }
966         // if a namespace functor, instantiate the functor (if not done already) and store it (*2)
967         if (functor == null && namespace instanceof JexlContext.NamespaceFunctor) {
968             functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
969         }
970         // got a functor, store it and return it
971         if (functor != null) {
972             synchronized (this) {
973                 if (functors == null) {
974                     functors = new HashMap<>();
975                 }
976                 functors.put(prefix, functor);
977             }
978             return functor;
979         }
980         return namespace;
981     }
982 
983     /**
984      * Sets an attribute of an object.
985      *
986      * @param object    to set the value to
987      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
988      * @param value     the value to assign to the object's attribute
989      * @param node      the node that evaluated as the object
990      */
991     protected void setAttribute(final Object object, final Object attribute, final Object value, final JexlNode node) {
992         cancelCheck(node);
993         Exception xcause = null;
994         try {
995             final JexlOperator operator;
996             if (node != null) {
997                 // attempt to reuse last executor cached in volatile JexlNode.value
998                 if (cache) {
999                     final Object cached = node.jjtGetValue();
1000                     if (cached instanceof JexlPropertySet) {
1001                         final JexlPropertySet setter = (JexlPropertySet) cached;
1002                         final Object eval = setter.tryInvoke(object, attribute, value);
1003                         if (!setter.tryFailed(eval)) {
1004                             return;
1005                         }
1006                     }
1007                 }
1008                 // try operator overload
1009                 operator = node.jjtGetParent() instanceof ASTArrayAccess ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
1010                 final Object result = operators.tryOverload(node, operator, object, attribute, value);
1011                 if (result != JexlEngine.TRY_FAILED) {
1012                     return;
1013                 }
1014             } else {
1015                 operator = JexlOperator.PROPERTY_SET;
1016             }
1017             // resolve that property
1018             final List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
1019             JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
1020             // if we can't find an exact match, narrow the value argument and try again
1021             if (vs == null) {
1022                 // replace all numbers with the smallest type that will fit
1023                 final Object[] narrow = {value};
1024                 if (arithmetic.narrowArguments(narrow)) {
1025                     vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
1026                 }
1027             }
1028             if (vs != null) {
1029                 // cache executor in volatile JexlNode.value
1030                 vs.invoke(object, value);
1031                 if (node != null && cache && vs.isCacheable()) {
1032                     node.jjtSetValue(vs);
1033                 }
1034                 return;
1035             }
1036         } catch (final Exception xany) {
1037             xcause = xany;
1038         }
1039         // lets fail
1040         if (node == null) {
1041             // direct call
1042             final String error = "unable to set object property"
1043                     + ", class: " + object.getClass().getName()
1044                     + ", property: " + attribute
1045                     + ", argument: " + value.getClass().getSimpleName();
1046             throw new UnsupportedOperationException(error, xcause);
1047         }
1048         final String attrStr = Objects.toString(attribute, null);
1049         unsolvableProperty(node, attrStr, true, xcause);
1050     }
1051 
1052     /**
1053      * Sets a variable in the global context.
1054      * <p>If interpretation applies lexical shade, the variable must exist (ie
1055      * the context has(...) method returns true) otherwise an error occurs.
1056      *
1057      * @param node the node
1058      * @param name the variable name
1059      * @param value the variable value
1060      */
1061     protected void setContextVariable(final JexlNode node, final String name, final Object value) {
1062         boolean lexical = options.isLexicalShade();
1063         if (!lexical && node instanceof ASTIdentifier) {
1064             lexical = ((ASTIdentifier) node).isLexical();
1065         }
1066         if (lexical && !context.has(name)) {
1067             throw new JexlException.Variable(node, name, true);
1068         }
1069         try {
1070             context.set(name, value);
1071         } catch (final UnsupportedOperationException xsupport) {
1072             throw new JexlException(node, "context is readonly", xsupport);
1073         }
1074     }
1075 
1076     /**
1077      * Pretty-prints a failing property (de)reference.
1078      * <p>Used by calls to unsolvableProperty(...).</p>
1079      *
1080      * @param node the property node
1081      * @return the (pretty) string
1082      */
1083     protected String stringifyProperty(final JexlNode node) {
1084         if (node instanceof ASTArrayAccess) {
1085             return "[" + stringifyPropertyValue(node.jjtGetChild(0)) + "]";
1086         }
1087         if (node instanceof ASTMethodNode) {
1088             return stringifyPropertyValue(node.jjtGetChild(0));
1089         }
1090         if (node instanceof ASTFunctionNode) {
1091             return stringifyPropertyValue(node.jjtGetChild(0));
1092         }
1093         if (node instanceof ASTIdentifier) {
1094             return ((ASTIdentifier) node).getName();
1095         }
1096         if (node instanceof ASTReference) {
1097             return stringifyProperty(node.jjtGetChild(0));
1098         }
1099         return stringifyPropertyValue(node);
1100     }
1101 
1102     /**
1103      * Triggered when a variable is lexically known as undefined.
1104      *
1105      * @param node  the node where the error originated from
1106      * @param variable   the variable name
1107      * @return throws JexlException if strict and not silent, null otherwise
1108      */
1109     protected Object undefinedVariable(final JexlNode node, final String variable) {
1110         return variableError(node, variable, VariableIssue.UNDEFINED);
1111     }
1112 
1113     /**
1114      * Triggered when a method cannot be resolved.
1115      *
1116      * @param node   the node where the error originated from
1117      * @param method the method name
1118      * @return throws JexlException if strict and not silent, null otherwise
1119      */
1120     protected Object unsolvableMethod(final JexlNode node, final String method) {
1121         return unsolvableMethod(node, method, null);
1122     }
1123 
1124     /**
1125      * Triggered when a method cannot be resolved.
1126      *
1127      * @param node   the node where the error originated from
1128      * @param method the method name
1129      * @param args the method arguments
1130      * @return throws JexlException if strict and not silent, null otherwise
1131      */
1132     protected Object unsolvableMethod(final JexlNode node, final String method, final Object[] args) {
1133         if (isStrictEngine()) {
1134             throw new JexlException.Method(node, method, args);
1135         }
1136         if (logger.isDebugEnabled()) {
1137             logger.debug(JexlException.methodError(node, method, args));
1138         }
1139         return null;
1140     }
1141 
1142     /**
1143      * Triggered when a property cannot be resolved.
1144      *
1145      * @param node  the node where the error originated from
1146      * @param property   the property node
1147      * @param cause the cause if any
1148      * @param undef whether the property is undefined or null
1149      * @return throws JexlException if strict and not silent, null otherwise
1150      */
1151     protected Object unsolvableProperty(final JexlNode node, final String property, final boolean undef, final Throwable cause) {
1152         if (isStrictEngine() && !isTernaryProtected(node)) {
1153             throw new JexlException.Property(node, property, undef, cause);
1154         }
1155         if (logger.isDebugEnabled()) {
1156             logger.debug(JexlException.propertyError(node, property, undef));
1157         }
1158         return null;
1159     }
1160 
1161     /**
1162      * Triggered when a variable cannot be resolved.
1163      *
1164      * @param node  the node where the error originated from
1165      * @param variable   the variable name
1166      * @param undef whether the variable is undefined or null
1167      * @return throws JexlException if strict and not silent, null otherwise
1168      */
1169     protected Object unsolvableVariable(final JexlNode node, final String variable, final boolean undef) {
1170         return variableError(node, variable, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
1171     }
1172 
1173     /**
1174      * Triggered when a variable generates an issue.
1175      *
1176      * @param node  the node where the error originated from
1177      * @param variable   the variable name
1178      * @param issue the issue type
1179      * @return throws JexlException if strict and not silent, null otherwise
1180      */
1181     protected Object variableError(final JexlNode node, final String variable, final VariableIssue issue) {
1182         if (isStrictEngine() && !isTernaryProtected(node)) {
1183             throw new JexlException.Variable(node, variable, issue);
1184         }
1185         if (logger.isDebugEnabled()) {
1186             logger.debug(JexlException.variableError(node, variable, issue));
1187         }
1188         return null;
1189     }
1190 }