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