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