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         final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
621                 ? JexlOperator.ARRAY_GET : JexlOperator.PROPERTY_GET;
622         final Object result = operators.tryOverload(node, operator, object, attribute);
623         if (result != JexlEngine.TRY_FAILED) {
624             return result;
625         }
626         Exception xcause = null;
627         try {
628             // attempt to reuse last executor cached in volatile JexlNode.value
629             if (node != null && cache) {
630                 final Object cached = node.jjtGetValue();
631                 if (cached instanceof JexlPropertyGet) {
632                     final JexlPropertyGet vg = (JexlPropertyGet) cached;
633                     final Object value = vg.tryInvoke(object, attribute);
634                     if (!vg.tryFailed(value)) {
635                         return value;
636                     }
637                 }
638             }
639             // resolve that property
640             final List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
641             final JexlPropertyGet vg = uberspect.getPropertyGet(resolvers, object, attribute);
642             if (vg != null) {
643                 final Object value = vg.invoke(object);
644                 // cache executor in volatile JexlNode.value
645                 if (node != null && cache && vg.isCacheable()) {
646                     node.jjtSetValue(vg);
647                 }
648                 return value;
649             }
650         } catch (final Exception xany) {
651             xcause = xany;
652         }
653         // lets fail
654         if (node == null) {
655             // direct call
656             final String error = "unable to get object property"
657                     + ", class: " + object.getClass().getName()
658                     + ", property: " + attribute;
659             throw new UnsupportedOperationException(error, xcause);
660         }
661         final boolean safe = node instanceof ASTIdentifierAccess && ((ASTIdentifierAccess) node).isSafe();
662         if (safe) {
663             return null;
664         }
665         final String attrStr = Objects.toString(attribute, null);
666         return unsolvableProperty(node, attrStr, true, xcause);
667     }
668 
669     /**
670      * Gets a value of a defined local variable or from the context.
671      *
672      * @param frame the local frame
673      * @param block the lexical block if any
674      * @param identifier the variable node
675      * @return the value
676      */
677     protected Object getVariable(final Frame frame, final LexicalScope block, final ASTIdentifier identifier) {
678         final int symbol = identifier.getSymbol();
679         final String name = identifier.getName();
680         // if we have a symbol, we have a scope thus a frame
681         if ((options.isLexicalShade() || identifier.isLexical()) && identifier.isShaded()) {
682             return undefinedVariable(identifier, name);
683         }
684         // a local var ?
685         if (symbol >= 0 && frame.has(symbol)) {
686             final Object value = frame.get(symbol);
687             // not out of scope with no lexical shade ?
688             if (value != Scope.UNDEFINED) {
689                 // null operand of an arithmetic operator ?
690                 if (value == null && isStrictOperand(identifier)) {
691                     return unsolvableVariable(identifier, name, false); // defined but null
692                 }
693                 return value;
694             }
695         }
696         // consider global
697         final Object value = context.get(name);
698         // is it null ?
699         if (value == null) {
700             // is it defined ?
701             if (!context.has(name)) {
702                 // not defined, ignore in some cases...
703                 final boolean ignore = identifier.jjtGetParent() instanceof ASTReference
704                         || isSafe() && (symbol >= 0 || identifier.jjtGetParent() instanceof ASTAssignment);
705                 if (!ignore) {
706                     return undefinedVariable(identifier, name); // undefined
707                 }
708             } else if (isStrictOperand(identifier)) {
709                 return unsolvableVariable(identifier, name, false); // defined but null
710             }
711         }
712         return value;
713     }
714 
715     /**
716      * Triggered when method, function or constructor invocation fails with an exception.
717      *
718      * @param node       the node triggering the exception
719      * @param methodName the method/function name
720      * @param xany       the cause
721      * @return a JexlException that will be thrown
722      */
723     protected JexlException invocationException(final JexlNode node, final String methodName, final Throwable xany) {
724         final Throwable cause = xany.getCause();
725         if (cause instanceof JexlException) {
726             return (JexlException) cause;
727         }
728         if (cause instanceof InterruptedException) {
729             return new JexlException.Cancel(node);
730         }
731         return new JexlException(node, methodName, xany);
732     }
733 
734     /**
735      * @return true if interrupt throws a JexlException.Cancel.
736      */
737     protected boolean isCancellable() {
738         return options.isCancellable();
739     }
740 
741     /**
742      * Checks whether this interpreter execution was cancelled due to thread interruption.
743      *
744      * @return true if cancelled, false otherwise
745      */
746     protected boolean isCancelled() {
747         return cancelled.get() || Thread.currentThread().isInterrupted();
748     }
749 
750     /**
751      * Whether this interpreter ignores null in navigation expression as errors.
752      *
753      * @return true if safe, false otherwise
754      */
755     protected boolean isSafe() {
756         return options.isSafe();
757     }
758 
759     /**
760      * Whether this interpreter is currently evaluating with a silent mode.
761      *
762      * @return true if silent, false otherwise
763      */
764     protected boolean isSilent() {
765         return options.isSilent();
766     }
767 
768     /**
769      * Whether this interpreter is currently evaluating with a strict engine flag.
770      *
771      * @return true if strict engine, false otherwise
772      */
773     protected boolean isStrictEngine() {
774         return options.isStrict();
775     }
776 
777     /**
778      * @param node the operand node
779      * @return true if this node is an operand of a strict operator, false otherwise
780      */
781     protected boolean isStrictOperand(final JexlNode node) {
782        return node.jjtGetParent().isStrictOperator(arithmetic);
783     }
784 
785     /**
786      * Check if a null evaluated expression is protected by a ternary expression.
787      * <p>
788      * The rationale is that the ternary / elvis expressions are meant for the user to explicitly take control
789      * over the error generation; ie, ternaries can return null even if the engine in strict mode
790      * would normally throw an exception.
791      * </p>
792      *
793      * @return true if nullable variable, false otherwise
794      */
795     protected boolean isTernaryProtected(final JexlNode startNode) {
796         JexlNode node = startNode;
797         for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
798             // protect only the condition part of the ternary
799             if (walk instanceof ASTTernaryNode
800                     || walk instanceof ASTNullpNode) {
801                 return node == walk.jjtGetChild(0);
802             }
803             if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
804                 break;
805             }
806             node = walk;
807         }
808         return false;
809     }
810 
811     /**
812      * Checks whether a variable is defined.
813      * <p>The var may be either a local variable declared in the frame and
814      * visible from the block or defined in the context.
815      *
816      * @param frame the frame
817      * @param block the block
818      * @param name the variable name
819      * @return true if variable is defined, false otherwise
820      */
821     protected boolean isVariableDefined(final Frame frame, final LexicalScope block, final String name) {
822         if (frame != null && block != null) {
823             final Integer ref = frame.getScope().getSymbol(name);
824             final int symbol = ref != null ? ref : -1;
825             if (symbol >= 0  && block.hasSymbol(symbol)) {
826                 final Object value = frame.get(symbol);
827                 return value != Scope.UNDEFINED && value != Scope.UNDECLARED;
828             }
829         }
830         return context.has(name);
831     }
832 
833     /**
834      * Triggered when an operator fails.
835      *
836      * @param node     the node where the error originated from
837      * @param operator the operator symbol
838      * @param cause    the cause of error (if any)
839      * @return throws JexlException if strict and not silent, null otherwise
840      */
841     protected Object operatorError(final JexlNode node, final JexlOperator operator, final Throwable cause) {
842         if (isStrictEngine()) {
843             throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
844         }
845         if (logger.isDebugEnabled()) {
846             logger.debug(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
847         }
848         return null;
849     }
850 
851     /**
852      * Triggered when a variable is lexically known as being redefined.
853      *
854      * @param node  the node where the error originated from
855      * @param variable   the variable name
856      * @return throws JexlException if strict and not silent, null otherwise
857      */
858     protected Object redefinedVariable(final JexlNode node, final String variable) {
859         return variableError(node, variable, VariableIssue.REDEFINED);
860     }
861 
862     /**
863      * Resolves a namespace, eventually allocating an instance using context as constructor argument.
864      * <p>
865      * The lifetime of such instances span the current expression or script evaluation.</p>
866      *
867      * @param prefix the prefix name (can be null for global namespace)
868      * @param node   the AST node
869      * @return the namespace instance
870      */
871     protected Object resolveNamespace(final String prefix, final JexlNode node) {
872         Object namespace;
873         // check whether this namespace is a functor
874         synchronized (this) {
875             if (functors != null) {
876                 namespace = functors.get(prefix);
877                 if (namespace != null) {
878                     return namespace;
879                 }
880             }
881         }
882         // check if namespace is a resolver
883         namespace = ns.resolveNamespace(prefix);
884         if (namespace == null) {
885             namespace = functions.get(prefix);
886             if (namespace == null) {
887                 namespace = jexl.getNamespace(prefix);
888             }
889             if (prefix != null && namespace == null) {
890                 throw new JexlException(node, "no such function namespace " + prefix, null);
891             }
892         }
893         Object functor = null;
894         // class or string (*1)
895         if (namespace instanceof Class<?> || namespace instanceof String) {
896             // the namespace(d) identifier
897             final ASTIdentifier nsNode = (ASTIdentifier) node.jjtGetChild(0);
898             final boolean cacheable = cache && prefix != null;
899             final Object cached = cacheable ? nsNode.jjtGetValue() : null;
900             // we know the class is used as namespace of static methods, no functor
901             if (cached instanceof Class<?>) {
902                 return cached;
903             }
904             // attempt to reuse last cached constructor
905             if (cached instanceof JexlContext.NamespaceFunctor) {
906                 final Object eval = ((JexlContext.NamespaceFunctor) cached).createFunctor(context);
907                 if (JexlEngine.TRY_FAILED != eval) {
908                     functor = eval;
909                     namespace = cached;
910                 }
911             }
912             if (functor == null) {
913                 // find a constructor with that context as argument or without
914                 for (int tried = 0; tried < 2; ++tried) {
915                     final boolean withContext = tried == 0;
916                     final JexlMethod ctor = withContext
917                             ? uberspect.getConstructor(namespace, context)
918                             : uberspect.getConstructor(namespace);
919                     if (ctor != null) {
920                         try {
921                             functor = withContext
922                                     ? ctor.invoke(namespace, context)
923                                     : ctor.invoke(namespace);
924                             // defensive
925                             if (functor != null) {
926                                 // wrap the namespace in a NamespaceFunctor to shield us from the actual
927                                 // number of arguments to call it with.
928                                 final Object nsFinal = namespace;
929                                 // make it a class (not a lambda!) so instanceof (see *2) will catch it
930                                 namespace = (NamespaceFunctor) context -> withContext
931                                         ? ctor.tryInvoke(null, nsFinal, context)
932                                         : ctor.tryInvoke(null, nsFinal);
933                                 if (cacheable && ctor.isCacheable()) {
934                                     nsNode.jjtSetValue(namespace);
935                                 }
936                                 break; // we found a constructor that did create a functor
937                             }
938                         } catch (final Exception xinst) {
939                             throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
940                         }
941                     }
942                 }
943                 // did not, will not create a functor instance; use a class, namespace of static methods
944                 if (functor == null) {
945                     try {
946                         // try to find a class with that name
947                         if (namespace instanceof String) {
948                             namespace = uberspect.getClassLoader().loadClass((String) namespace);
949                         }
950                         // we know it's a class in all cases (see *1)
951                         if (cacheable) {
952                             nsNode.jjtSetValue(namespace);
953                         }
954                     } catch (final ClassNotFoundException e) {
955                         // not a class
956                         throw new JexlException(node, "no such class namespace " + prefix, e);
957                     }
958                 }
959             }
960         }
961         // if a namespace functor, instantiate the functor (if not done already) and store it (*2)
962         if (functor == null && namespace instanceof JexlContext.NamespaceFunctor) {
963             functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
964         }
965         // got a functor, store it and return it
966         if (functor != null) {
967             synchronized (this) {
968                 if (functors == null) {
969                     functors = new HashMap<>();
970                 }
971                 functors.put(prefix, functor);
972             }
973             return functor;
974         }
975         return namespace;
976     }
977 
978     /**
979      * Sets an attribute of an object.
980      *
981      * @param object    to set the value to
982      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or key for a map
983      * @param value     the value to assign to the object's attribute
984      * @param node      the node that evaluated as the object
985      */
986     protected void setAttribute(final Object object, final Object attribute, final Object value, final JexlNode node) {
987         cancelCheck(node);
988         final JexlOperator operator = node != null && node.jjtGetParent() instanceof ASTArrayAccess
989                                       ? JexlOperator.ARRAY_SET : JexlOperator.PROPERTY_SET;
990         final Object result = operators.tryOverload(node, operator, object, attribute, value);
991         if (result != JexlEngine.TRY_FAILED) {
992             return;
993         }
994         Exception xcause = null;
995         try {
996             // attempt to reuse last executor cached in volatile JexlNode.value
997             if (node != null && cache) {
998                 final Object cached = node.jjtGetValue();
999                 if (cached instanceof JexlPropertySet) {
1000                     final JexlPropertySet setter = (JexlPropertySet) cached;
1001                     final Object eval = setter.tryInvoke(object, attribute, value);
1002                     if (!setter.tryFailed(eval)) {
1003                         return;
1004                     }
1005                 }
1006             }
1007             final List<JexlUberspect.PropertyResolver> resolvers = uberspect.getResolvers(operator, object);
1008             JexlPropertySet vs = uberspect.getPropertySet(resolvers, object, attribute, value);
1009             // if we can't find an exact match, narrow the value argument and try again
1010             if (vs == null) {
1011                 // replace all numbers with the smallest type that will fit
1012                 final Object[] narrow = {value};
1013                 if (arithmetic.narrowArguments(narrow)) {
1014                     vs = uberspect.getPropertySet(resolvers, object, attribute, narrow[0]);
1015                 }
1016             }
1017             if (vs != null) {
1018                 // cache executor in volatile JexlNode.value
1019                 vs.invoke(object, value);
1020                 if (node != null && cache && vs.isCacheable()) {
1021                     node.jjtSetValue(vs);
1022                 }
1023                 return;
1024             }
1025         } catch (final Exception xany) {
1026             xcause = xany;
1027         }
1028         // lets fail
1029         if (node == null) {
1030             // direct call
1031             final String error = "unable to set object property"
1032                     + ", class: " + object.getClass().getName()
1033                     + ", property: " + attribute
1034                     + ", argument: " + value.getClass().getSimpleName();
1035             throw new UnsupportedOperationException(error, xcause);
1036         }
1037         final String attrStr = Objects.toString(attribute, null);
1038         unsolvableProperty(node, attrStr, true, xcause);
1039     }
1040 
1041     /**
1042      * Sets a variable in the global context.
1043      * <p>If interpretation applies lexical shade, the variable must exist (ie
1044      * the context has(...) method returns true) otherwise an error occurs.
1045      *
1046      * @param node the node
1047      * @param name the variable name
1048      * @param value the variable value
1049      */
1050     protected void setContextVariable(final JexlNode node, final String name, final Object value) {
1051         boolean lexical = options.isLexicalShade();
1052         if (!lexical && node instanceof ASTIdentifier) {
1053             lexical = ((ASTIdentifier) node).isLexical();
1054         }
1055         if (lexical && !context.has(name)) {
1056             throw new JexlException.Variable(node, name, true);
1057         }
1058         try {
1059             context.set(name, value);
1060         } catch (final UnsupportedOperationException xsupport) {
1061             throw new JexlException(node, "context is readonly", xsupport);
1062         }
1063     }
1064 
1065     /**
1066      * Pretty-prints a failing property (de)reference.
1067      * <p>Used by calls to unsolvableProperty(...).</p>
1068      *
1069      * @param node the property node
1070      * @return the (pretty) string
1071      */
1072     protected String stringifyProperty(final JexlNode node) {
1073         if (node instanceof ASTArrayAccess) {
1074             return "[" + stringifyPropertyValue(node.jjtGetChild(0)) + "]";
1075         }
1076         if (node instanceof ASTMethodNode) {
1077             return stringifyPropertyValue(node.jjtGetChild(0));
1078         }
1079         if (node instanceof ASTFunctionNode) {
1080             return stringifyPropertyValue(node.jjtGetChild(0));
1081         }
1082         if (node instanceof ASTIdentifier) {
1083             return ((ASTIdentifier) node).getName();
1084         }
1085         if (node instanceof ASTReference) {
1086             return stringifyProperty(node.jjtGetChild(0));
1087         }
1088         return stringifyPropertyValue(node);
1089     }
1090 
1091     /**
1092      * Triggered when a variable is lexically known as undefined.
1093      *
1094      * @param node  the node where the error originated from
1095      * @param variable   the variable name
1096      * @return throws JexlException if strict and not silent, null otherwise
1097      */
1098     protected Object undefinedVariable(final JexlNode node, final String variable) {
1099         return variableError(node, variable, VariableIssue.UNDEFINED);
1100     }
1101 
1102     /**
1103      * Triggered when a method cannot be resolved.
1104      *
1105      * @param node   the node where the error originated from
1106      * @param method the method name
1107      * @return throws JexlException if strict and not silent, null otherwise
1108      */
1109     protected Object unsolvableMethod(final JexlNode node, final String method) {
1110         return unsolvableMethod(node, method, null);
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      * @param args the method arguments
1119      * @return throws JexlException if strict and not silent, null otherwise
1120      */
1121     protected Object unsolvableMethod(final JexlNode node, final String method, final Object[] args) {
1122         if (isStrictEngine()) {
1123             throw new JexlException.Method(node, method, args);
1124         }
1125         if (logger.isDebugEnabled()) {
1126             logger.debug(JexlException.methodError(node, method, args));
1127         }
1128         return null;
1129     }
1130 
1131     /**
1132      * Triggered when a property cannot be resolved.
1133      *
1134      * @param node  the node where the error originated from
1135      * @param property   the property node
1136      * @param cause the cause if any
1137      * @param undef whether the property is undefined or null
1138      * @return throws JexlException if strict and not silent, null otherwise
1139      */
1140     protected Object unsolvableProperty(final JexlNode node, final String property, final boolean undef, final Throwable cause) {
1141         if (isStrictEngine() && !isTernaryProtected(node)) {
1142             throw new JexlException.Property(node, property, undef, cause);
1143         }
1144         if (logger.isDebugEnabled()) {
1145             logger.debug(JexlException.propertyError(node, property, undef));
1146         }
1147         return null;
1148     }
1149 
1150     /**
1151      * Triggered when a variable cannot be resolved.
1152      *
1153      * @param node  the node where the error originated from
1154      * @param variable   the variable name
1155      * @param undef whether the variable is undefined or null
1156      * @return throws JexlException if strict and not silent, null otherwise
1157      */
1158     protected Object unsolvableVariable(final JexlNode node, final String variable, final boolean undef) {
1159         return variableError(node, variable, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
1160     }
1161 
1162     /**
1163      * Triggered when a variable generates an issue.
1164      *
1165      * @param node  the node where the error originated from
1166      * @param variable   the variable name
1167      * @param issue the issue type
1168      * @return throws JexlException if strict and not silent, null otherwise
1169      */
1170     protected Object variableError(final JexlNode node, final String variable, final VariableIssue issue) {
1171         if (isStrictEngine() && !isTernaryProtected(node)) {
1172             throw new JexlException.Variable(node, variable, issue);
1173         }
1174         if (logger.isDebugEnabled()) {
1175             logger.debug(JexlException.variableError(node, variable, issue));
1176         }
1177         return null;
1178     }
1179 }