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