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.jexl2;
18  
19  import java.lang.reflect.Array;
20  import java.lang.reflect.InvocationTargetException;
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.commons.jexl2.parser.SimpleNode;
28  import org.apache.commons.logging.Log;
29  
30  import org.apache.commons.jexl2.parser.ASTFloatLiteral;
31  import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
32  import org.apache.commons.jexl2.parser.JexlNode;
33  import org.apache.commons.jexl2.parser.ASTAdditiveNode;
34  import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
35  import org.apache.commons.jexl2.parser.ASTAndNode;
36  import org.apache.commons.jexl2.parser.ASTAmbiguous;
37  import org.apache.commons.jexl2.parser.ASTArrayAccess;
38  import org.apache.commons.jexl2.parser.ASTArrayLiteral;
39  import org.apache.commons.jexl2.parser.ASTAssignment;
40  import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
41  import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
42  import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
43  import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
44  import org.apache.commons.jexl2.parser.ASTBlock;
45  import org.apache.commons.jexl2.parser.ASTConstructorNode;
46  import org.apache.commons.jexl2.parser.ASTDivNode;
47  import org.apache.commons.jexl2.parser.ASTEQNode;
48  import org.apache.commons.jexl2.parser.ASTERNode;
49  import org.apache.commons.jexl2.parser.ASTEmptyFunction;
50  import org.apache.commons.jexl2.parser.ASTFalseNode;
51  import org.apache.commons.jexl2.parser.ASTFunctionNode;
52  import org.apache.commons.jexl2.parser.ASTForeachStatement;
53  import org.apache.commons.jexl2.parser.ASTGENode;
54  import org.apache.commons.jexl2.parser.ASTGTNode;
55  import org.apache.commons.jexl2.parser.ASTIdentifier;
56  import org.apache.commons.jexl2.parser.ASTIfStatement;
57  import org.apache.commons.jexl2.parser.ASTJexlScript;
58  import org.apache.commons.jexl2.parser.ASTLENode;
59  import org.apache.commons.jexl2.parser.ASTLTNode;
60  import org.apache.commons.jexl2.parser.ASTMapEntry;
61  import org.apache.commons.jexl2.parser.ASTMapLiteral;
62  import org.apache.commons.jexl2.parser.ASTMethodNode;
63  import org.apache.commons.jexl2.parser.ASTModNode;
64  import org.apache.commons.jexl2.parser.ASTMulNode;
65  import org.apache.commons.jexl2.parser.ASTNENode;
66  import org.apache.commons.jexl2.parser.ASTNRNode;
67  import org.apache.commons.jexl2.parser.ASTNotNode;
68  import org.apache.commons.jexl2.parser.ASTNullLiteral;
69  import org.apache.commons.jexl2.parser.ASTNumberLiteral;
70  import org.apache.commons.jexl2.parser.ASTOrNode;
71  import org.apache.commons.jexl2.parser.ASTReference;
72  import org.apache.commons.jexl2.parser.ASTReferenceExpression;
73  import org.apache.commons.jexl2.parser.ASTReturnStatement;
74  import org.apache.commons.jexl2.parser.ASTSizeFunction;
75  import org.apache.commons.jexl2.parser.ASTSizeMethod;
76  import org.apache.commons.jexl2.parser.ASTStringLiteral;
77  import org.apache.commons.jexl2.parser.ASTTernaryNode;
78  import org.apache.commons.jexl2.parser.ASTTrueNode;
79  import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
80  import org.apache.commons.jexl2.parser.ASTWhileStatement;
81  import org.apache.commons.jexl2.parser.Node;
82  import org.apache.commons.jexl2.parser.ParserVisitor;
83  
84  import org.apache.commons.jexl2.introspection.Uberspect;
85  import org.apache.commons.jexl2.introspection.JexlMethod;
86  import org.apache.commons.jexl2.introspection.JexlPropertyGet;
87  import org.apache.commons.jexl2.introspection.JexlPropertySet;
88  import org.apache.commons.jexl2.parser.ASTVar;
89  
90  /**
91   * An interpreter of JEXL syntax.
92   *
93   * @since 2.0
94   */
95  public class Interpreter implements ParserVisitor {
96      /** The logger. */
97      protected final Log logger;
98      /** The uberspect. */
99      protected final Uberspect uberspect;
100     /** The arithmetic handler. */
101     protected final JexlArithmetic arithmetic;
102     /** The map of registered functions. */
103     protected final Map<String, Object> functions;
104     /** The map of registered functions. */
105     protected Map<String, Object> functors;
106     /** The context to store/retrieve variables. */
107     protected final JexlContext context;
108     /** Strict interpreter flag. Do not modify; will be made final/private in a later version. */
109     protected boolean strict;
110     /** Silent intepreter flag.  Do not modify; will be made final/private in a later version. */
111     protected boolean silent;
112     /** Cache executors. */
113     protected final boolean cache;
114     /** Registers or arguments. */
115     protected Object[] registers = null;
116     /**
117      * Parameter names if any.
118      * Intended for use in debugging; not currently used externally.
119      * @since 2.1 
120      */
121     @SuppressWarnings("unused")
122     private String[] parameters = null;
123 
124     /** 
125      * Cancellation support.
126      * @see #isCancelled()
127      * @since 2.1
128      */
129     private volatile boolean cancelled = false;
130 
131     /** Empty parameters for method matching. */
132     protected static final Object[] EMPTY_PARAMS = new Object[0];
133 
134     /**
135      * Creates an interpreter.
136      * @param jexl the engine creating this interpreter
137      * @param aContext the context to evaluate expression
138      * @deprecated 
139      */
140     @Deprecated
141     public Interpreter(JexlEngine jexl, JexlContext aContext) {
142         this(jexl, aContext, !jexl.isLenient(), jexl.isSilent());
143     }
144 
145     /**
146      * Creates an interpreter.
147      * @param jexl the engine creating this interpreter
148      * @param aContext the context to evaluate expression
149      * @param strictFlag whether this interpreter runs in strict mode
150      * @param silentFlag whether this interpreter runs in silent mode
151      * @since 2.1
152      */
153     public Interpreter(JexlEngine jexl, JexlContext aContext, boolean strictFlag, boolean silentFlag) {
154         this.logger = jexl.logger;
155         this.uberspect = jexl.uberspect;
156         this.arithmetic = jexl.arithmetic;
157         this.functions = jexl.functions;
158         this.strict = strictFlag;
159         this.silent = silentFlag;
160         this.cache = jexl.cache != null;
161         this.context = aContext != null? aContext : JexlEngine.EMPTY_CONTEXT;
162         this.functors = null;
163     }
164 
165     /**
166      * Copy constructor.
167      * @param base the base to copy
168      * @since 2.1
169      */
170     protected Interpreter(Interpreter base) {
171         this.logger = base.logger;
172         this.uberspect = base.uberspect;
173         this.arithmetic = base.arithmetic;
174         this.functions = base.functions;
175         this.strict = base.strict;
176         this.silent = base.silent;
177         this.cache = base.cache;
178         this.context = base.context;
179         this.functors = base.functors;
180     }
181     
182     /**
183      * Sets whether this interpreter considers unknown variables, methods and constructors as errors.
184      * @param flag true for strict, false for lenient
185      * @deprecated Do not use; will be removed in a later version
186      * @since 2.1 
187      */
188     // TODO why add a method and then deprecate it?
189     @Deprecated
190     public void setStrict(boolean flag) {
191         this.strict = flag;
192     }
193 
194     /**
195      * Sets whether this interpreter throws JexlException when encountering errors.
196      * @param flag true for silent, false for verbose
197      * @deprecated Do not use; will be removed in a later version
198      */
199     @Deprecated
200     public void setSilent(boolean flag) {
201         this.silent = flag;
202     }
203 
204     /**
205      * Checks whether this interpreter considers unknown variables, methods and constructors as errors.
206      * @return true if strict, false otherwise
207      * @since 2.1
208      */
209     public boolean isStrict() {
210         return this.strict;
211     }
212 
213     /**
214      * Checks whether this interpreter throws JexlException when encountering errors.
215      * @return true if silent, false otherwise
216      */
217     public boolean isSilent() {
218         return this.silent;
219     }
220 
221     /**
222      * Interpret the given script/expression.
223      * <p>
224      * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
225      * </p>
226      * @param node the script or expression to interpret.
227      * @return the result of the interpretation.
228      * @throws JexlException if any error occurs during interpretation.
229      */
230     public Object interpret(JexlNode node) {
231         try {
232             return node.jjtAccept(this, null);
233         } catch (JexlException.Return xreturn) {
234             Object value = xreturn.getValue();
235             return value;
236         } catch (JexlException xjexl) {
237             if (silent) {
238                 logger.warn(xjexl.getMessage(), xjexl.getCause());
239                 return null;
240             }
241             throw xjexl;
242         } finally {
243             functors = null;
244             parameters = null;
245             registers = null;
246         }
247     }
248     
249     /**
250      * Gets the context.
251      * @return the {@link JexlContext} used for evaluation.
252      * @since 2.1
253      */
254     protected JexlContext getContext() {
255         return context;
256     }
257 
258     /**
259      * Gets the uberspect.
260      * @return an {@link Uberspect}
261      */
262     protected Uberspect getUberspect() {
263         return uberspect;
264     }
265 
266     /**
267      * Sets this interpreter registers for bean access/assign expressions.
268      * <p>Use setFrame(...) instead.</p>
269      * @param theRegisters the array of registers
270      */
271     @Deprecated
272     protected void setRegisters(Object... theRegisters) {
273         if (theRegisters != null) {
274             String[] regStrs = new String[theRegisters.length];
275             for (int r = 0; r < regStrs.length; ++r) {
276                 regStrs[r] = "#" + r;
277             }
278             this.parameters = regStrs;
279         }
280         this.registers = theRegisters;
281     }
282 
283     /**
284      * Sets this interpreter parameters and arguments.
285      * @param frame the calling frame
286      * @since 2.1
287      */
288     protected void setFrame(JexlEngine.Frame frame) {
289         if (frame != null) {
290             this.parameters = frame.getParameters();
291             this.registers = frame.getRegisters();
292         } else {
293             this.parameters = null;
294             this.registers = null;
295         }
296     }
297 
298     /**
299      * Finds the node causing a NPE for diadic operators.
300      * @param xrt the RuntimeException
301      * @param node the parent node
302      * @param left the left argument
303      * @param right the right argument
304      * @return the left, right or parent node
305      */
306     protected JexlNode findNullOperand(RuntimeException xrt, JexlNode node, Object left, Object right) {
307         if (xrt instanceof ArithmeticException
308                 && JexlException.NULL_OPERAND == xrt.getMessage()) {
309             if (left == null) {
310                 return node.jjtGetChild(0);
311             }
312             if (right == null) {
313                 return node.jjtGetChild(1);
314             }
315         }
316         return node;
317     }
318 
319     /**
320      * Triggered when variable can not be resolved.
321      * @param xjexl the JexlException ("undefined variable " + variable)
322      * @return throws JexlException if strict, null otherwise
323      */
324     protected Object unknownVariable(JexlException xjexl) {
325         if (strict) {
326             throw xjexl;
327         }
328         if (!silent) {
329             logger.warn(xjexl.getMessage());
330         }
331         return null;
332     }
333 
334     /**
335      * Triggered when method, function or constructor invocation fails.
336      * @param xjexl the JexlException wrapping the original error
337      * @return throws JexlException if strict, null otherwise
338      */
339     protected Object invocationFailed(JexlException xjexl) {
340         if (strict || xjexl instanceof JexlException.Return) {
341             throw xjexl;
342         }
343         if (!silent) {
344             logger.warn(xjexl.getMessage(), xjexl.getCause());
345         }
346         return null;
347     }
348 
349     /**
350      * Checks whether this interpreter execution was cancelled due to thread interruption.
351      * @return true if cancelled, false otherwise
352      * @since 2.1
353      */
354     protected boolean isCancelled() {
355         if (cancelled | Thread.interrupted()) {
356             cancelled = true;
357         }
358         return cancelled;
359     }
360 
361     /**
362      * Resolves a namespace, eventually allocating an instance using context as constructor argument.
363      * The lifetime of such instances span the current expression or script evaluation.
364      *
365      * @param prefix the prefix name (may be null for global namespace)
366      * @param node the AST node
367      * @return the namespace instance
368      */
369     protected Object resolveNamespace(String prefix, JexlNode node) {
370         Object namespace = null;
371         // check whether this namespace is a functor
372         if (functors != null) {
373             namespace = functors.get(prefix);
374             if (namespace != null) {
375                 return namespace;
376             }
377         }
378         // check if namespace if a resolver
379         if (context instanceof NamespaceResolver) {
380             namespace = ((NamespaceResolver) context).resolveNamespace(prefix);
381         }
382         if (namespace == null) {
383             namespace = functions.get(prefix);
384             if (prefix != null && namespace == null) {
385                 throw new JexlException(node, "no such function namespace " + prefix);
386             }
387         }
388         // allow namespace to be instantiated as functor with context if possible, not an error otherwise
389         if (namespace instanceof Class<?>) {
390             Object[] args = new Object[]{context};
391             JexlMethod ctor = uberspect.getConstructorMethod(namespace, args, node);
392             if (ctor != null) {
393                 try {
394                     namespace = ctor.invoke(namespace, args);
395                     if (functors == null) {
396                         functors = new HashMap<String, Object>();
397                     }
398                     functors.put(prefix, namespace);
399                 } catch (Exception xinst) {
400                     throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
401                 }
402             }
403         }
404         return namespace;
405     }
406 
407     /** {@inheritDoc} */
408     public Object visit(ASTAdditiveNode node, Object data) {
409         /**
410          * The pattern for exception mgmt is to let the child*.jjtAccept
411          * out of the try/catch loop so that if one fails, the ex will
412          * traverse up to the interpreter.
413          * In cases where this is not convenient/possible, JexlException must
414          * be caught explicitly and rethrown.
415          */
416         Object left = node.jjtGetChild(0).jjtAccept(this, data);
417         for (int c = 2, size = node.jjtGetNumChildren(); c < size; c += 2) {
418             Object right = node.jjtGetChild(c).jjtAccept(this, data);
419             try {
420                 JexlNode op = node.jjtGetChild(c - 1);
421                 if (op instanceof ASTAdditiveOperator) {
422                     String which = op.image;
423                     if ("+".equals(which)) {
424                         left = arithmetic.add(left, right);
425                         continue;
426                     }
427                     if ("-".equals(which)) {
428                         left = arithmetic.subtract(left, right);
429                         continue;
430                     }
431                     throw new UnsupportedOperationException("unknown operator " + which);
432                 }
433                 throw new IllegalArgumentException("unknown operator " + op);
434             } catch (ArithmeticException xrt) {
435                 JexlNode xnode = findNullOperand(xrt, node, left, right);
436                 throw new JexlException(xnode, "+/- error", xrt);
437             }
438         }
439         return left;
440     }
441 
442     /** {@inheritDoc} */
443     public Object visit(ASTAdditiveOperator node, Object data) {
444         throw new UnsupportedOperationException("Shoud not be called.");
445     }
446 
447     /** {@inheritDoc} */
448     public Object visit(ASTAndNode node, Object data) {
449         Object left = node.jjtGetChild(0).jjtAccept(this, data);
450         try {
451             boolean leftValue = arithmetic.toBoolean(left);
452             if (!leftValue) {
453                 return Boolean.FALSE;
454             }
455         } catch (RuntimeException xrt) {
456             throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
457         }
458         Object right = node.jjtGetChild(1).jjtAccept(this, data);
459         try {
460             boolean rightValue = arithmetic.toBoolean(right);
461             if (!rightValue) {
462                 return Boolean.FALSE;
463             }
464         } catch (ArithmeticException xrt) {
465             throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
466         }
467         return Boolean.TRUE;
468     }
469 
470     /** {@inheritDoc} */
471     public Object visit(ASTArrayAccess node, Object data) {
472         // first objectNode is the identifier
473         Object object = node.jjtGetChild(0).jjtAccept(this, data);
474         // can have multiple nodes - either an expression, integer literal or reference
475         int numChildren = node.jjtGetNumChildren();
476         for (int i = 1; i < numChildren; i++) {
477             JexlNode nindex = node.jjtGetChild(i);
478             if (nindex instanceof JexlNode.Literal<?>) {
479                 object = nindex.jjtAccept(this, object);
480             } else {
481                 Object index = nindex.jjtAccept(this, null);
482                 object = getAttribute(object, index, nindex);
483             }
484         }
485 
486         return object;
487     }
488 
489     /** {@inheritDoc} */
490     public Object visit(ASTArrayLiteral node, Object data) {
491         Object literal = node.getLiteral();
492         if (literal == null) {
493             int childCount = node.jjtGetNumChildren();
494             Object[] array = new Object[childCount];
495             for (int i = 0; i < childCount; i++) {
496                 Object entry = node.jjtGetChild(i).jjtAccept(this, data);
497                 array[i] = entry;
498             }
499             literal = arithmetic.narrowArrayType(array);
500             node.setLiteral(literal);
501         }
502         return literal;
503     }
504 
505     /** {@inheritDoc} */
506     public Object visit(ASTAssignment node, Object data) {
507         // left contains the reference to assign to
508         int register = -1;
509         JexlNode left = node.jjtGetChild(0);
510         if (left instanceof ASTIdentifier) {
511             ASTIdentifier var = (ASTIdentifier) left;
512             register = var.getRegister();
513             if (register < 0) {
514                 throw new JexlException(left, "unknown variable " + left.image);
515             }
516         } else if (!(left instanceof ASTReference)) {
517             throw new JexlException(left, "illegal assignment form 0");
518         }
519         // right is the value expression to assign
520         Object right = node.jjtGetChild(1).jjtAccept(this, data);
521 
522         // determine initial object & property:
523         JexlNode objectNode = null;
524         Object object = register >= 0 ? registers[register] : null;
525         JexlNode propertyNode = null;
526         Object property = null;
527         boolean isVariable = true;
528         int v = 0;
529         StringBuilder variableName = null;
530         // 1: follow children till penultimate, resolve dot/array
531         int last = left.jjtGetNumChildren() - 1;
532         // check we are not assigning a register itself
533         boolean isRegister = last < 0 && register >= 0;
534         // start at 1 if register
535         for (int c = register >= 0 ? 1 : 0; c < last; ++c) {
536             objectNode = left.jjtGetChild(c);
537             // evaluate the property within the object
538             object = objectNode.jjtAccept(this, object);
539             if (object != null) {
540                 continue;
541             }
542             isVariable &= objectNode instanceof ASTIdentifier
543                     || (objectNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) objectNode).isInteger());
544             // if we get null back as a result, check for an ant variable
545             if (isVariable) {
546                 if (v == 0) {
547                     variableName = new StringBuilder(left.jjtGetChild(0).image);
548                     v = 1;
549                 }
550                 for (; v <= c; ++v) {
551                     variableName.append('.');
552                     variableName.append(left.jjtGetChild(v).image);
553                 }
554                 object = context.get(variableName.toString());
555                 // disallow mixing ant & bean with same root; avoid ambiguity
556                 if (object != null) {
557                     isVariable = false;
558                 }
559             } else {
560                 throw new JexlException(objectNode, "illegal assignment form");
561             }
562         }
563         // 2: last objectNode will perform assignement in all cases
564         propertyNode = isRegister ? null : left.jjtGetChild(last);
565         boolean antVar = false;
566         if (propertyNode instanceof ASTIdentifier) {
567             ASTIdentifier identifier = (ASTIdentifier) propertyNode;
568             register = identifier.getRegister();
569             if (register >= 0) {
570                 isRegister = true;
571             } else {
572                 property = identifier.image;
573                 antVar = true;
574             }
575         } else if (propertyNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) propertyNode).isInteger()) {
576             property = ((ASTNumberLiteral) propertyNode).getLiteral();
577             antVar = true;
578         } else if (propertyNode instanceof ASTArrayAccess) {
579             // first objectNode is the identifier
580             objectNode = propertyNode;
581             ASTArrayAccess narray = (ASTArrayAccess) objectNode;
582             Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
583             if (nobject == null) {
584                 throw new JexlException(objectNode, "array element is null");
585             } else {
586                 object = nobject;
587             }
588             // can have multiple nodes - either an expression, integer literal or
589             // reference
590             last = narray.jjtGetNumChildren() - 1;
591             for (int i = 1; i < last; i++) {
592                 objectNode = narray.jjtGetChild(i);
593                 if (objectNode instanceof JexlNode.Literal<?>) {
594                     object = objectNode.jjtAccept(this, object);
595                 } else {
596                     Object index = objectNode.jjtAccept(this, null);
597                     object = getAttribute(object, index, objectNode);
598                 }
599             }
600             property = narray.jjtGetChild(last).jjtAccept(this, null);
601         } else if (!isRegister) {
602             throw new JexlException(objectNode, "illegal assignment form");
603         }
604         // deal with ant variable; set context
605         if (isRegister) {
606             registers[register] = right;
607             return right;
608         } else if (antVar) {
609             if (isVariable && object == null) {
610                 if (variableName != null) {
611                     if (last > 0) {
612                         variableName.append('.');
613                     }
614                     variableName.append(property);
615                     property = variableName.toString();
616                 }
617                 try {
618                     context.set(String.valueOf(property), right);
619                 } catch (UnsupportedOperationException xsupport) {
620                     throw new JexlException(node, "context is readonly", xsupport);
621                 }
622                 return right;
623             }
624         }
625         if (property == null) {
626             // no property, we fail
627             throw new JexlException(propertyNode, "property is null");
628         }
629         if (object == null) {
630             // no object, we fail
631             throw new JexlException(objectNode, "bean is null");
632         }
633         // one before last, assign
634         setAttribute(object, property, right, propertyNode);
635         return right;
636     }
637 
638     /** {@inheritDoc} */
639     public Object visit(ASTBitwiseAndNode node, Object data) {
640         Object left = node.jjtGetChild(0).jjtAccept(this, data);
641         Object right = node.jjtGetChild(1).jjtAccept(this, data);
642         try {
643             return arithmetic.bitwiseAnd(left, right);
644         } catch (ArithmeticException xrt) {
645             throw new JexlException(node, "& error", xrt);
646         }
647     }
648 
649     /** {@inheritDoc} */
650     public Object visit(ASTBitwiseComplNode node, Object data) {
651         Object left = node.jjtGetChild(0).jjtAccept(this, data);
652         try {
653             return arithmetic.bitwiseComplement(left);
654         } catch (ArithmeticException xrt) {
655             throw new JexlException(node, "~ error", xrt);
656         }
657     }
658 
659     /** {@inheritDoc} */
660     public Object visit(ASTBitwiseOrNode node, Object data) {
661         Object left = node.jjtGetChild(0).jjtAccept(this, data);
662         Object right = node.jjtGetChild(1).jjtAccept(this, data);
663         try {
664             return arithmetic.bitwiseOr(left, right);
665         } catch (ArithmeticException xrt) {
666             throw new JexlException(node, "| error", xrt);
667         }
668     }
669 
670     /** {@inheritDoc} */
671     public Object visit(ASTBitwiseXorNode node, Object data) {
672         Object left = node.jjtGetChild(0).jjtAccept(this, data);
673         Object right = node.jjtGetChild(1).jjtAccept(this, data);
674         try {
675             return arithmetic.bitwiseXor(left, right);
676         } catch (ArithmeticException xrt) {
677             throw new JexlException(node, "^ error", xrt);
678         }
679     }
680 
681     /** {@inheritDoc} */
682     public Object visit(ASTBlock node, Object data) {
683         int numChildren = node.jjtGetNumChildren();
684         Object result = null;
685         for (int i = 0; i < numChildren; i++) {
686             result = node.jjtGetChild(i).jjtAccept(this, data);
687         }
688         return result;
689     }
690 
691     /** {@inheritDoc} */
692     public Object visit(ASTDivNode node, Object data) {
693         Object left = node.jjtGetChild(0).jjtAccept(this, data);
694         Object right = node.jjtGetChild(1).jjtAccept(this, data);
695         try {
696             return arithmetic.divide(left, right);
697         } catch (ArithmeticException xrt) {
698             if (!strict) {
699                 return new Double(0.0);
700             }
701             JexlNode xnode = findNullOperand(xrt, node, left, right);
702             throw new JexlException(xnode, "divide error", xrt);
703         }
704     }
705 
706     /** {@inheritDoc} */
707     public Object visit(ASTEmptyFunction node, Object data) {
708         Object o = node.jjtGetChild(0).jjtAccept(this, data);
709         if (o == null) {
710             return Boolean.TRUE;
711         }
712         if (o instanceof String && "".equals(o)) {
713             return Boolean.TRUE;
714         }
715         if (o.getClass().isArray() && ((Object[]) o).length == 0) {
716             return Boolean.TRUE;
717         }
718         if (o instanceof Collection<?>) {
719             return ((Collection<?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
720         }
721         // Map isn't a collection
722         if (o instanceof Map<?, ?>) {
723             return ((Map<?, ?>) o).isEmpty() ? Boolean.TRUE : Boolean.FALSE;
724         }
725         return Boolean.FALSE;
726     }
727 
728     /** {@inheritDoc} */
729     public Object visit(ASTEQNode node, Object data) {
730         Object left = node.jjtGetChild(0).jjtAccept(this, data);
731         Object right = node.jjtGetChild(1).jjtAccept(this, data);
732         try {
733             return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
734         } catch (ArithmeticException xrt) {
735             throw new JexlException(node, "== error", xrt);
736         }
737     }
738 
739     /** {@inheritDoc} */
740     public Object visit(ASTFalseNode node, Object data) {
741         return Boolean.FALSE;
742     }
743 
744     /** {@inheritDoc} */
745     public Object visit(ASTForeachStatement node, Object data) {
746         Object result = null;
747         /* first objectNode is the loop variable */
748         ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
749         ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
750         int register = loopVariable.getRegister();
751         /* second objectNode is the variable to iterate */
752         Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
753         // make sure there is a value to iterate on and a statement to execute
754         if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
755             /* third objectNode is the statement to execute */
756             JexlNode statement = node.jjtGetChild(2);
757             // get an iterator for the collection/array etc via the
758             // introspector.
759             Iterator<?> itemsIterator = uberspect.getIterator(iterableValue, node);
760             if (itemsIterator != null) {
761                 while (itemsIterator.hasNext()) {
762                     if (isCancelled()) {
763                         throw new JexlException.Cancel(node);
764                     }
765                     // set loopVariable to value of iterator
766                     Object value = itemsIterator.next();
767                     if (register < 0) {
768                         context.set(loopVariable.image, value);
769                     } else {
770                         registers[register] = value;
771                     }
772                     // execute statement
773                     result = statement.jjtAccept(this, data);
774                 }
775             }
776         }
777         return result;
778     }
779 
780     /** {@inheritDoc} */
781     public Object visit(ASTGENode node, Object data) {
782         Object left = node.jjtGetChild(0).jjtAccept(this, data);
783         Object right = node.jjtGetChild(1).jjtAccept(this, data);
784         try {
785             return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
786         } catch (ArithmeticException xrt) {
787             throw new JexlException(node, ">= error", xrt);
788         }
789     }
790 
791     /** {@inheritDoc} */
792     public Object visit(ASTGTNode node, Object data) {
793         Object left = node.jjtGetChild(0).jjtAccept(this, data);
794         Object right = node.jjtGetChild(1).jjtAccept(this, data);
795         try {
796             return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
797         } catch (ArithmeticException xrt) {
798             throw new JexlException(node, "> error", xrt);
799         }
800     }
801 
802     /** {@inheritDoc} */
803     public Object visit(ASTERNode node, Object data) {
804         Object left = node.jjtGetChild(0).jjtAccept(this, data);
805         Object right = node.jjtGetChild(1).jjtAccept(this, data);
806         try {
807             // use arithmetic / pattern matching ?
808             if (right instanceof java.util.regex.Pattern || right instanceof String) {
809                 return arithmetic.matches(left, right) ? Boolean.TRUE : Boolean.FALSE;
810             }
811             // left in right ? <=> right.contains(left) ?
812             // try contains on collection
813             if (right instanceof Set<?>) {
814                 return ((Set<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
815             }
816             // try contains on map key
817             if (right instanceof Map<?, ?>) {
818                 return ((Map<?, ?>) right).containsKey(left) ? Boolean.TRUE : Boolean.FALSE;
819             }
820             // try contains on collection
821             if (right instanceof Collection<?>) {
822                 return ((Collection<?>) right).contains(left) ? Boolean.TRUE : Boolean.FALSE;
823             }
824             // try a contains method (duck type set)
825             try {
826                 Object[] argv = {left};
827                 JexlMethod vm = uberspect.getMethod(right, "contains", argv, node);
828                 if (vm != null) {
829                     return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE;
830                 } else if (arithmetic.narrowArguments(argv)) {
831                     vm = uberspect.getMethod(right, "contains", argv, node);
832                     if (vm != null) {
833                         return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.TRUE : Boolean.FALSE;
834                     }
835                 }
836             } catch (InvocationTargetException e) {
837                 throw new JexlException(node, "=~ invocation error", e.getCause());
838             } catch (Exception e) {
839                 throw new JexlException(node, "=~ error", e);
840             }
841             // try iterative comparison
842             Iterator<?> it = uberspect.getIterator(right, node);
843             if (it != null) {
844                 while (it.hasNext()) {
845                     Object next = it.next();
846                     if (next == left || (next != null && next.equals(left))) {
847                         return Boolean.TRUE;
848                     }
849                 }
850                 return Boolean.FALSE;
851             }
852             // defaults to equal
853             return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
854         } catch (ArithmeticException xrt) {
855             throw new JexlException(node, "=~ error", xrt);
856         }
857     }
858 
859     /** {@inheritDoc} */
860     public Object visit(ASTIdentifier node, Object data) {
861         if (isCancelled()) {
862             throw new JexlException.Cancel(node);
863         }
864         String name = node.image;
865         if (data == null) {
866             int register = node.getRegister();
867             if (register >= 0) {
868                 return registers[register];
869             }
870             Object value = context.get(name);
871             if (value == null
872                     && !(node.jjtGetParent() instanceof ASTReference)
873                     && !context.has(name)
874                     && !isTernaryProtected(node)) {
875                 JexlException xjexl = new JexlException.Variable(node, name);
876                 return unknownVariable(xjexl);
877             }
878             return value;
879         } else {
880             return getAttribute(data, name, node);
881         }
882     }
883 
884     /**
885      * @deprecated Do not use
886      */
887     @Deprecated
888     public Object visit(ASTFloatLiteral node, Object data) {
889         throw new UnsupportedOperationException("Method should not be called; only present for API compatibiltiy");
890     }
891 
892     /**
893      * @deprecated Do not use
894      */
895     @Deprecated
896     public Object visit(ASTIntegerLiteral node, Object data) {
897         throw new UnsupportedOperationException("Method should not be called; only present for API compatibiltiy");
898     }
899 
900     /** {@inheritDoc} */
901     public Object visit(ASTVar node, Object data) {
902         return visit((ASTIdentifier) node, data);
903     }
904 
905     /** {@inheritDoc} */
906     public Object visit(ASTIfStatement node, Object data) {
907         int n = 0;
908         try {
909             Object result = null;
910             /* first objectNode is the condition */
911             Object expression = node.jjtGetChild(0).jjtAccept(this, data);
912             if (arithmetic.toBoolean(expression)) {
913                 // first objectNode is true statement
914                 n = 1;
915                 result = node.jjtGetChild(1).jjtAccept(this, data);
916             } else {
917                 // if there is a false, execute it. false statement is the second
918                 // objectNode
919                 if (node.jjtGetNumChildren() == 3) {
920                     n = 2;
921                     result = node.jjtGetChild(2).jjtAccept(this, data);
922                 }
923             }
924             return result;
925         } catch (JexlException error) {
926             throw error;
927         } catch (ArithmeticException xrt) {
928             throw new JexlException(node.jjtGetChild(n), "if error", xrt);
929         }
930     }
931 
932     /** {@inheritDoc} */
933     public Object visit(ASTNumberLiteral node, Object data) {
934         if (data != null && node.isInteger()) {
935             return getAttribute(data, node.getLiteral(), node);
936         }
937         return node.getLiteral();
938     }
939 
940     /** {@inheritDoc} */
941     public Object visit(ASTJexlScript node, Object data) {
942         int numChildren = node.jjtGetNumChildren();
943         Object result = null;
944         for (int i = 0; i < numChildren; i++) {
945             JexlNode child = node.jjtGetChild(i);
946             result = child.jjtAccept(this, data);
947         }
948         return result;
949     }
950 
951     /** {@inheritDoc} */
952     public Object visit(ASTLENode node, Object data) {
953         Object left = node.jjtGetChild(0).jjtAccept(this, data);
954         Object right = node.jjtGetChild(1).jjtAccept(this, data);
955         try {
956             return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
957         } catch (ArithmeticException xrt) {
958             throw new JexlException(node, "<= error", xrt);
959         }
960     }
961 
962     /** {@inheritDoc} */
963     public Object visit(ASTLTNode node, Object data) {
964         Object left = node.jjtGetChild(0).jjtAccept(this, data);
965         Object right = node.jjtGetChild(1).jjtAccept(this, data);
966         try {
967             return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
968         } catch (ArithmeticException xrt) {
969             throw new JexlException(node, "< error", xrt);
970         }
971     }
972 
973     /** {@inheritDoc} */
974     public Object visit(ASTMapEntry node, Object data) {
975         Object key = node.jjtGetChild(0).jjtAccept(this, data);
976         Object value = node.jjtGetChild(1).jjtAccept(this, data);
977         return new Object[]{key, value};
978     }
979 
980     /** {@inheritDoc} */
981     public Object visit(ASTMapLiteral node, Object data) {
982         int childCount = node.jjtGetNumChildren();
983         Map<Object, Object> map = new HashMap<Object, Object>();
984 
985         for (int i = 0; i < childCount; i++) {
986             Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
987             map.put(entry[0], entry[1]);
988         }
989 
990         return map;
991     }
992 
993     /**
994      * Calls a method (or function).
995      * <p>
996      * Method resolution is a follows:
997      * 1 - attempt to find a method in the bean passed as parameter;
998      * 2 - if this fails, narrow the arguments and try again
999      * 3 - if this still fails, seeks a Script or JexlMethod as a property of that bean.
1000      * </p>
1001      * @param node the method node
1002      * @param bean the bean this method should be invoked upon
1003      * @param methodNode the node carrying the method name
1004      * @param argb the first argument index, child of the method node
1005      * @return the result of the method invocation
1006      */
1007     private Object call(JexlNode node, Object bean, ASTIdentifier methodNode, int argb) {
1008         if (isCancelled()) {
1009             throw new JexlException.Cancel(node);
1010         }
1011         String methodName = methodNode.image;
1012         // evaluate the arguments
1013         int argc = node.jjtGetNumChildren() - argb;
1014         Object[] argv = new Object[argc];
1015         for (int i = 0; i < argc; i++) {
1016             argv[i] = node.jjtGetChild(i + argb).jjtAccept(this, null);
1017         }
1018 
1019         JexlException xjexl = null;
1020         try {
1021             // attempt to reuse last executor cached in volatile JexlNode.value
1022             if (cache) {
1023                 Object cached = node.jjtGetValue();
1024                 if (cached instanceof JexlMethod) {
1025                     JexlMethod me = (JexlMethod) cached;
1026                     Object eval = me.tryInvoke(methodName, bean, argv);
1027                     if (!me.tryFailed(eval)) {
1028                         return eval;
1029                     }
1030                 }
1031             }
1032             boolean cacheable = cache;
1033             JexlMethod vm = uberspect.getMethod(bean, methodName, argv, node);
1034             // DG: If we can't find an exact match, narrow the parameters and try again
1035             if (vm == null) {
1036                 if (arithmetic.narrowArguments(argv)) {
1037                     vm = uberspect.getMethod(bean, methodName, argv, node);
1038                 }
1039                 if (vm == null) {
1040                     Object functor = null;
1041                     // could not find a method, try as a var
1042                     if (bean == context) {
1043                         int register = methodNode.getRegister();
1044                         if (register >= 0) {
1045                             functor = registers[register];
1046                         } else {
1047                             functor = context.get(methodName);
1048                         }
1049                     } else {
1050                         JexlPropertyGet gfunctor = uberspect.getPropertyGet(bean, methodName, node);
1051                         if (gfunctor != null) {
1052                             functor = gfunctor.tryInvoke(bean, methodName);
1053                         }
1054                     }
1055                     // script of jexl method will do
1056                     if (functor instanceof Script) {
1057                         return ((Script) functor).execute(context, argv.length > 0 ? argv : null);
1058                     } else if (functor instanceof JexlMethod) {
1059                         vm = (JexlMethod) functor;
1060                         cacheable = false;
1061                     } else {
1062                         xjexl = new JexlException.Method(node, methodName);
1063                     }
1064                 }
1065             }
1066             if (xjexl == null) {
1067                 // vm cannot be null if xjexl is null
1068                 Object eval = vm.invoke(bean, argv);
1069                 // cache executor in volatile JexlNode.value
1070                 if (cacheable && vm.isCacheable()) {
1071                     node.jjtSetValue(vm);
1072                 }
1073                 return eval;
1074             }
1075         } catch (InvocationTargetException e) {
1076             xjexl = new JexlException(node, "method invocation error", e.getCause());
1077         } catch (Exception e) {
1078             xjexl = new JexlException(node, "method error", e);
1079         }
1080         return invocationFailed(xjexl);
1081     }
1082 
1083     /** {@inheritDoc} */
1084     public Object visit(ASTMethodNode node, Object data) {
1085         // the object to invoke the method on should be in the data argument
1086         if (data == null) {
1087             // if the method node is the first child of the (ASTReference) parent,
1088             // it is considered as calling a 'top level' function
1089             if (node.jjtGetParent().jjtGetChild(0) == node) {
1090                 data = resolveNamespace(null, node);
1091                 if (data == null) {
1092                     data = context;
1093                 }
1094             } else {
1095                 throw new JexlException(node, "attempting to call method on null");
1096             }
1097         }
1098         // objectNode 0 is the identifier (method name), the others are parameters.
1099         ASTIdentifier methodNode = (ASTIdentifier) node.jjtGetChild(0);
1100         return call(node, data, methodNode, 1);
1101     }
1102 
1103     /** {@inheritDoc} */
1104     public Object visit(ASTFunctionNode node, Object data) {
1105         // objectNode 0 is the prefix
1106         String prefix = node.jjtGetChild(0).image;
1107         Object namespace = resolveNamespace(prefix, node);
1108         // objectNode 1 is the identifier , the others are parameters.
1109         ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
1110         return call(node, namespace, functionNode, 2);
1111     }
1112 
1113     /** {@inheritDoc} */
1114     public Object visit(ASTConstructorNode node, Object data) {
1115         if (isCancelled()) {
1116             throw new JexlException.Cancel(node);
1117         }
1118         // first child is class or class name
1119         Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
1120         // get the ctor args
1121         int argc = node.jjtGetNumChildren() - 1;
1122         Object[] argv = new Object[argc];
1123         for (int i = 0; i < argc; i++) {
1124             argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
1125         }
1126 
1127         JexlException xjexl = null;
1128         try {
1129             // attempt to reuse last constructor cached in volatile JexlNode.value
1130             if (cache) {
1131                 Object cached = node.jjtGetValue();
1132                 if (cached instanceof JexlMethod) {
1133                     JexlMethod mctor = (JexlMethod) cached;
1134                     Object eval = mctor.tryInvoke(null, cobject, argv);
1135                     if (!mctor.tryFailed(eval)) {
1136                         return eval;
1137                     }
1138                 }
1139             }
1140             JexlMethod ctor = uberspect.getConstructorMethod(cobject, argv, node);
1141             // DG: If we can't find an exact match, narrow the parameters and try again
1142             if (ctor == null) {
1143                 if (arithmetic.narrowArguments(argv)) {
1144                     ctor = uberspect.getConstructorMethod(cobject, argv, node);
1145                 }
1146                 if (ctor == null) {
1147                     xjexl = new JexlException.Method(node, cobject.toString());
1148                 }
1149             }
1150             if (xjexl == null) {
1151                 Object instance = ctor.invoke(cobject, argv);
1152                 // cache executor in volatile JexlNode.value
1153                 if (cache && ctor.isCacheable()) {
1154                     node.jjtSetValue(ctor);
1155                 }
1156                 return instance;
1157             }
1158         } catch (InvocationTargetException e) {
1159             xjexl = new JexlException(node, "constructor invocation error", e.getCause());
1160         } catch (Exception e) {
1161             xjexl = new JexlException(node, "constructor error", e);
1162         }
1163         return invocationFailed(xjexl);
1164     }
1165 
1166     /** {@inheritDoc} */
1167     public Object visit(ASTModNode node, Object data) {
1168         Object left = node.jjtGetChild(0).jjtAccept(this, data);
1169         Object right = node.jjtGetChild(1).jjtAccept(this, data);
1170         try {
1171             return arithmetic.mod(left, right);
1172         } catch (ArithmeticException xrt) {
1173             if (!strict) {
1174                 return new Double(0.0);
1175             }
1176             JexlNode xnode = findNullOperand(xrt, node, left, right);
1177             throw new JexlException(xnode, "% error", xrt);
1178         }
1179     }
1180 
1181     /** {@inheritDoc} */
1182     public Object visit(ASTMulNode node, Object data) {
1183         Object left = node.jjtGetChild(0).jjtAccept(this, data);
1184         Object right = node.jjtGetChild(1).jjtAccept(this, data);
1185         try {
1186             return arithmetic.multiply(left, right);
1187         } catch (ArithmeticException xrt) {
1188             JexlNode xnode = findNullOperand(xrt, node, left, right);
1189             throw new JexlException(xnode, "* error", xrt);
1190         }
1191     }
1192 
1193     /** {@inheritDoc} */
1194     public Object visit(ASTNENode node, Object data) {
1195         Object left = node.jjtGetChild(0).jjtAccept(this, data);
1196         Object right = node.jjtGetChild(1).jjtAccept(this, data);
1197         try {
1198             return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
1199         } catch (ArithmeticException xrt) {
1200             JexlNode xnode = findNullOperand(xrt, node, left, right);
1201             throw new JexlException(xnode, "!= error", xrt);
1202         }
1203     }
1204 
1205     /** {@inheritDoc} */
1206     public Object visit(ASTNRNode node, Object data) {
1207         Object left = node.jjtGetChild(0).jjtAccept(this, data);
1208         Object right = node.jjtGetChild(1).jjtAccept(this, data);
1209         try {
1210             if (right instanceof java.util.regex.Pattern || right instanceof String) {
1211                 // use arithmetic / pattern matching
1212                 return arithmetic.matches(left, right) ? Boolean.FALSE : Boolean.TRUE;
1213             }
1214             // try contains on collection
1215             if (right instanceof Set<?>) {
1216                 return ((Set<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
1217             }
1218             // try contains on map key
1219             if (right instanceof Map<?, ?>) {
1220                 return ((Map<?, ?>) right).containsKey(left) ? Boolean.FALSE : Boolean.TRUE;
1221             }
1222             // try contains on collection
1223             if (right instanceof Collection<?>) {
1224                 return ((Collection<?>) right).contains(left) ? Boolean.FALSE : Boolean.TRUE;
1225             }
1226             // try a contains method (duck type set)
1227             try {
1228                 Object[] argv = {left};
1229                 JexlMethod vm = uberspect.getMethod(right, "contains", argv, node);
1230                 if (vm != null) {
1231                     return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE;
1232                 } else if (arithmetic.narrowArguments(argv)) {
1233                     vm = uberspect.getMethod(right, "contains", argv, node);
1234                     if (vm != null) {
1235                         return arithmetic.toBoolean(vm.invoke(right, argv)) ? Boolean.FALSE : Boolean.TRUE;
1236                     }
1237                 }
1238             } catch (InvocationTargetException e) {
1239                 throw new JexlException(node, "!~ invocation error", e.getCause());
1240             } catch (Exception e) {
1241                 throw new JexlException(node, "!~ error", e);
1242             }
1243             // try iterative comparison
1244             Iterator<?> it = uberspect.getIterator(right, node.jjtGetChild(1));
1245             if (it != null) {
1246                 while (it.hasNext()) {
1247                     Object next = it.next();
1248                     if (next == left || (next != null && next.equals(left))) {
1249                         return Boolean.FALSE;
1250                     }
1251                 }
1252                 return Boolean.TRUE;
1253             }
1254             // defaults to not equal
1255             return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
1256         } catch (ArithmeticException xrt) {
1257             throw new JexlException(node, "!~ error", xrt);
1258         }
1259     }
1260 
1261     /** {@inheritDoc} */
1262     public Object visit(ASTNotNode node, Object data) {
1263         Object val = node.jjtGetChild(0).jjtAccept(this, data);
1264         return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
1265     }
1266 
1267     /** {@inheritDoc} */
1268     public Object visit(ASTNullLiteral node, Object data) {
1269         return null;
1270     }
1271 
1272     /** {@inheritDoc} */
1273     public Object visit(ASTOrNode node, Object data) {
1274         Object left = node.jjtGetChild(0).jjtAccept(this, data);
1275         try {
1276             boolean leftValue = arithmetic.toBoolean(left);
1277             if (leftValue) {
1278                 return Boolean.TRUE;
1279             }
1280         } catch (ArithmeticException xrt) {
1281             throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
1282         }
1283         Object right = node.jjtGetChild(1).jjtAccept(this, data);
1284         try {
1285             boolean rightValue = arithmetic.toBoolean(right);
1286             if (rightValue) {
1287                 return Boolean.TRUE;
1288             }
1289         } catch (ArithmeticException xrt) {
1290             throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
1291         }
1292         return Boolean.FALSE;
1293     }
1294 
1295     /** {@inheritDoc} */
1296     public Object visit(ASTReference node, Object data) {
1297         // could be array access, identifier or map literal
1298         // followed by zero or more ("." and array access, method, size,
1299         // identifier or integer literal)
1300         int numChildren = node.jjtGetNumChildren();
1301         // pass first piece of data in and loop through children
1302         Object result = null;
1303         StringBuilder variableName = null;
1304         String propertyName = null;
1305         boolean isVariable = true;
1306         int v = 0;
1307         for (int c = 0; c < numChildren; c++) {
1308             if (isCancelled()) {
1309                 throw new JexlException.Cancel(node);
1310             }
1311             JexlNode theNode = node.jjtGetChild(c);
1312             // integer literals may be part of an antish var name only if no bean was found so far
1313             if (result == null && theNode instanceof ASTNumberLiteral && ((ASTNumberLiteral) theNode).isInteger()) {
1314                 isVariable &= v > 0;
1315             } else {
1316                 isVariable &= (theNode instanceof ASTIdentifier);
1317                 result = theNode.jjtAccept(this, result);
1318             }
1319             // if we get null back a result, check for an ant variable
1320             if (result == null && isVariable) {
1321                 if (v == 0) {
1322                     variableName = new StringBuilder(node.jjtGetChild(0).image);
1323                     v = 1;
1324                 }
1325                 for (; v <= c; ++v) {
1326                     variableName.append('.');
1327                     variableName.append(node.jjtGetChild(v).image);
1328                 }
1329                 result = context.get(variableName.toString());
1330             } else {
1331                 propertyName = theNode.image;
1332             }
1333         }
1334         if (result == null) {
1335             if (isVariable && !isTernaryProtected(node)
1336                     // variable unknow in context and not (from) a register
1337                     && !(context.has(variableName.toString())
1338                     || (numChildren == 1
1339                     && node.jjtGetChild(0) instanceof ASTIdentifier
1340                     && ((ASTIdentifier) node.jjtGetChild(0)).getRegister() >= 0))) {
1341                 JexlException xjexl = propertyName != null
1342                                       ? new JexlException.Property(node, propertyName)
1343                                       : new JexlException.Variable(node, variableName.toString());
1344                 return unknownVariable(xjexl);
1345             }
1346         }
1347         return result;
1348     }
1349 
1350     /** 
1351      * {@inheritDoc}
1352      * @since 2.1 
1353      */
1354     public Object visit(ASTReferenceExpression node, Object data) {
1355         ASTArrayAccess upper = node;
1356         return visit(upper, data);
1357     }
1358 
1359     /** 
1360      * {@inheritDoc}
1361      * @since 2.1 
1362      */
1363     public Object visit(ASTReturnStatement node, Object data) {
1364         Object val = node.jjtGetChild(0).jjtAccept(this, data);
1365         throw new JexlException.Return(node, null, val);
1366     }
1367 
1368     /**
1369      * Check if a null evaluated expression is protected by a ternary expression.
1370      * The rationale is that the ternary / elvis expressions are meant for the user to explictly take
1371      * control over the error generation; ie, ternaries can return null even if the engine in strict mode
1372      * would normally throw an exception.
1373      * @param node the expression node
1374      * @return true if nullable variable, false otherwise
1375      */
1376     private boolean isTernaryProtected(JexlNode node) {
1377         for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
1378             if (walk instanceof ASTTernaryNode) {
1379                 return true;
1380             } else if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
1381                 break;
1382             }
1383         }
1384         return false;
1385     }
1386 
1387     /** {@inheritDoc} */
1388     public Object visit(ASTSizeFunction node, Object data) {
1389         Object val = node.jjtGetChild(0).jjtAccept(this, data);
1390         if (val == null) {
1391             throw new JexlException(node, "size() : argument is null", null);
1392         }
1393         return Integer.valueOf(sizeOf(node, val));
1394     }
1395 
1396     /** {@inheritDoc} */
1397     public Object visit(ASTSizeMethod node, Object data) {
1398         return Integer.valueOf(sizeOf(node, data));
1399     }
1400 
1401     /** {@inheritDoc} */
1402     public Object visit(ASTStringLiteral node, Object data) {
1403         if (data != null) {
1404             return getAttribute(data, node.getLiteral(), node);
1405         }
1406         return node.image;
1407     }
1408 
1409     /** {@inheritDoc} */
1410     public Object visit(ASTTernaryNode node, Object data) {
1411         Object condition = node.jjtGetChild(0).jjtAccept(this, data);
1412         if (node.jjtGetNumChildren() == 3) {
1413             if (condition != null && arithmetic.toBoolean(condition)) {
1414                 return node.jjtGetChild(1).jjtAccept(this, data);
1415             } else {
1416                 return node.jjtGetChild(2).jjtAccept(this, data);
1417             }
1418         }
1419         if (condition != null && arithmetic.toBoolean(condition)) {
1420             return condition;
1421         } else {
1422             return node.jjtGetChild(1).jjtAccept(this, data);
1423         }
1424     }
1425 
1426     /** {@inheritDoc} */
1427     public Object visit(ASTTrueNode node, Object data) {
1428         return Boolean.TRUE;
1429     }
1430 
1431     /** {@inheritDoc} */
1432     public Object visit(ASTUnaryMinusNode node, Object data) {
1433         JexlNode valNode = node.jjtGetChild(0);
1434         Object val = valNode.jjtAccept(this, data);
1435         try {
1436             Object number = arithmetic.negate(val);
1437             // attempt to recoerce to literal class
1438             if (valNode instanceof ASTNumberLiteral && number instanceof Number) {
1439                 number = arithmetic.narrowNumber((Number) number, ((ASTNumberLiteral) valNode).getLiteralClass());
1440             }
1441             return number;
1442         } catch (ArithmeticException xrt) {
1443             throw new JexlException(valNode, "arithmetic error", xrt);
1444         }
1445     }
1446 
1447     /** {@inheritDoc} */
1448     public Object visit(ASTWhileStatement node, Object data) {
1449         Object result = null;
1450         /* first objectNode is the expression */
1451         Node expressionNode = node.jjtGetChild(0);
1452         while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
1453             if (isCancelled()) {
1454                 throw new JexlException.Cancel(node);
1455             }
1456             // execute statement
1457             if (node.jjtGetNumChildren() > 1) {
1458                 result = node.jjtGetChild(1).jjtAccept(this, data);
1459             }
1460         }
1461         return result;
1462     }
1463 
1464     /**
1465      * Calculate the <code>size</code> of various types: Collection, Array,
1466      * Map, String, and anything that has a int size() method.
1467      * @param node the node that gave the value to size
1468      * @param val the object to get the size of.
1469      * @return the size of val
1470      */
1471     private int sizeOf(JexlNode node, Object val) {
1472         if (val instanceof Collection<?>) {
1473             return ((Collection<?>) val).size();
1474         } else if (val.getClass().isArray()) {
1475             return Array.getLength(val);
1476         } else if (val instanceof Map<?, ?>) {
1477             return ((Map<?, ?>) val).size();
1478         } else if (val instanceof String) {
1479             return ((String) val).length();
1480         } else {
1481             // check if there is a size method on the object that returns an
1482             // integer and if so, just use it
1483             Object[] params = new Object[0];
1484             JexlMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, node);
1485             if (vm != null && vm.getReturnType() == Integer.TYPE) {
1486                 Integer result;
1487                 try {
1488                     result = (Integer) vm.invoke(val, params);
1489                 } catch (Exception e) {
1490                     throw new JexlException(node, "size() : error executing", e);
1491                 }
1492                 return result.intValue();
1493             }
1494             throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
1495         }
1496     }
1497 
1498     /**
1499      * Gets an attribute of an object.
1500      *
1501      * @param object to retrieve value from
1502      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1503      *            key for a map
1504      * @return the attribute value
1505      */
1506     public Object getAttribute(Object object, Object attribute) {
1507         return getAttribute(object, attribute, null);
1508     }
1509 
1510     /**
1511      * Gets an attribute of an object.
1512      *
1513      * @param object to retrieve value from
1514      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1515      *            key for a map
1516      * @param node the node that evaluated as the object
1517      * @return the attribute value
1518      */
1519     protected Object getAttribute(Object object, Object attribute, JexlNode node) {
1520         if (object == null) {
1521             throw new JexlException(node, "object is null");
1522         }
1523         if (isCancelled()) {
1524             throw new JexlException.Cancel(node);
1525         }
1526         // attempt to reuse last executor cached in volatile JexlNode.value
1527         if (node != null && cache) {
1528             Object cached = node.jjtGetValue();
1529             if (cached instanceof JexlPropertyGet) {
1530                 JexlPropertyGet vg = (JexlPropertyGet) cached;
1531                 Object value = vg.tryInvoke(object, attribute);
1532                 if (!vg.tryFailed(value)) {
1533                     return value;
1534                 }
1535             }
1536         }
1537         JexlPropertyGet vg = uberspect.getPropertyGet(object, attribute, node);
1538         if (vg != null) {
1539             try {
1540                 Object value = vg.invoke(object);
1541                 // cache executor in volatile JexlNode.value
1542                 if (node != null && cache && vg.isCacheable()) {
1543                     node.jjtSetValue(vg);
1544                 }
1545                 return value;
1546             } catch (Exception xany) {
1547                 if (node == null) {
1548                     throw new RuntimeException(xany);
1549                 } else {
1550                     JexlException xjexl = new JexlException.Property(node, attribute.toString());
1551                     if (strict) {
1552                         throw xjexl;
1553                     }
1554                     if (!silent) {
1555                         logger.warn(xjexl.getMessage());
1556                     }
1557                 }
1558             }
1559         }
1560         return null;
1561     }
1562 
1563     /**
1564      * Sets an attribute of an object.
1565      *
1566      * @param object to set the value to
1567      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1568      *            key for a map
1569      * @param value the value to assign to the object's attribute
1570      */
1571     public void setAttribute(Object object, Object attribute, Object value) {
1572         setAttribute(object, attribute, value, null);
1573     }
1574 
1575     /**
1576      * Sets an attribute of an object.
1577      *
1578      * @param object to set the value to
1579      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
1580      *            key for a map
1581      * @param value the value to assign to the object's attribute
1582      * @param node the node that evaluated as the object
1583      */
1584     protected void setAttribute(Object object, Object attribute, Object value, JexlNode node) {
1585         if (isCancelled()) {
1586             throw new JexlException.Cancel(node);
1587         }
1588         // attempt to reuse last executor cached in volatile JexlNode.value
1589         if (node != null && cache) {
1590             Object cached = node.jjtGetValue();
1591             if (cached instanceof JexlPropertySet) {
1592                 JexlPropertySet setter = (JexlPropertySet) cached;
1593                 Object eval = setter.tryInvoke(object, attribute, value);
1594                 if (!setter.tryFailed(eval)) {
1595                     return;
1596                 }
1597             }
1598         }
1599         JexlException xjexl = null;
1600         JexlPropertySet vs = uberspect.getPropertySet(object, attribute, value, node);
1601         // if we can't find an exact match, narrow the value argument and try again
1602         if (vs == null) {
1603             // replace all numbers with the smallest type that will fit
1604             Object[] narrow = {value};
1605             if (arithmetic.narrowArguments(narrow)) {
1606                 vs = uberspect.getPropertySet(object, attribute, narrow[0], node);
1607             }
1608         }
1609         if (vs != null) {
1610             try {
1611                 // cache executor in volatile JexlNode.value
1612                 vs.invoke(object, value);
1613                 if (node != null && cache && vs.isCacheable()) {
1614                     node.jjtSetValue(vs);
1615                 }
1616                 return;
1617             } catch (RuntimeException xrt) {
1618                 if (node == null) {
1619                     throw xrt;
1620                 }
1621                 xjexl = new JexlException(node, "set object property error", xrt);
1622             } catch (Exception xany) {
1623                 if (node == null) {
1624                     throw new RuntimeException(xany);
1625                 }
1626                 xjexl = new JexlException(node, "set object property error", xany);
1627             }
1628         }
1629         if (xjexl == null) {
1630             if (node == null) {
1631                 String error = "unable to set object property"
1632                         + ", class: " + object.getClass().getName()
1633                         + ", property: " + attribute
1634                         + ", argument: " + value.getClass().getSimpleName();
1635                 throw new UnsupportedOperationException(error);
1636             }
1637             xjexl = new JexlException.Property(node, attribute.toString());
1638         }
1639         if (strict) {
1640             throw xjexl;
1641         }
1642         if (!silent) {
1643             logger.warn(xjexl.getMessage());
1644         }
1645     }
1646 
1647     /**
1648      * Unused, satisfy ParserVisitor interface.
1649      * @param node a node
1650      * @param data the data
1651      * @return does not return
1652      */
1653     public Object visit(SimpleNode node, Object data) {
1654         throw new UnsupportedOperationException("Not supported yet.");
1655     }
1656 
1657     /**
1658      * Unused, should throw in Parser.
1659      * @param node a node
1660      * @param data the data
1661      * @return does not return
1662      */
1663     public Object visit(ASTAmbiguous node, Object data) {
1664         throw new UnsupportedOperationException("unexpected type of node");
1665     }
1666 }