001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.jexl2;
018    
019    import java.lang.reflect.Array;
020    import java.lang.reflect.InvocationTargetException;
021    import java.util.Collection;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import org.apache.commons.jexl2.parser.SimpleNode;
028    import org.apache.commons.logging.Log;
029    
030    import org.apache.commons.jexl2.parser.ASTFloatLiteral;
031    import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
032    import org.apache.commons.jexl2.parser.JexlNode;
033    import org.apache.commons.jexl2.parser.ASTAdditiveNode;
034    import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
035    import org.apache.commons.jexl2.parser.ASTAndNode;
036    import org.apache.commons.jexl2.parser.ASTAmbiguous;
037    import org.apache.commons.jexl2.parser.ASTArrayAccess;
038    import org.apache.commons.jexl2.parser.ASTArrayLiteral;
039    import org.apache.commons.jexl2.parser.ASTAssignment;
040    import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
041    import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
042    import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
043    import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
044    import org.apache.commons.jexl2.parser.ASTBlock;
045    import org.apache.commons.jexl2.parser.ASTConstructorNode;
046    import org.apache.commons.jexl2.parser.ASTDivNode;
047    import org.apache.commons.jexl2.parser.ASTEQNode;
048    import org.apache.commons.jexl2.parser.ASTERNode;
049    import org.apache.commons.jexl2.parser.ASTEmptyFunction;
050    import org.apache.commons.jexl2.parser.ASTFalseNode;
051    import org.apache.commons.jexl2.parser.ASTFunctionNode;
052    import org.apache.commons.jexl2.parser.ASTForeachStatement;
053    import org.apache.commons.jexl2.parser.ASTGENode;
054    import org.apache.commons.jexl2.parser.ASTGTNode;
055    import org.apache.commons.jexl2.parser.ASTIdentifier;
056    import org.apache.commons.jexl2.parser.ASTIfStatement;
057    import org.apache.commons.jexl2.parser.ASTJexlScript;
058    import org.apache.commons.jexl2.parser.ASTLENode;
059    import org.apache.commons.jexl2.parser.ASTLTNode;
060    import org.apache.commons.jexl2.parser.ASTMapEntry;
061    import org.apache.commons.jexl2.parser.ASTMapLiteral;
062    import org.apache.commons.jexl2.parser.ASTMethodNode;
063    import org.apache.commons.jexl2.parser.ASTModNode;
064    import org.apache.commons.jexl2.parser.ASTMulNode;
065    import org.apache.commons.jexl2.parser.ASTNENode;
066    import org.apache.commons.jexl2.parser.ASTNRNode;
067    import org.apache.commons.jexl2.parser.ASTNotNode;
068    import org.apache.commons.jexl2.parser.ASTNullLiteral;
069    import org.apache.commons.jexl2.parser.ASTNumberLiteral;
070    import org.apache.commons.jexl2.parser.ASTOrNode;
071    import org.apache.commons.jexl2.parser.ASTReference;
072    import org.apache.commons.jexl2.parser.ASTReferenceExpression;
073    import org.apache.commons.jexl2.parser.ASTReturnStatement;
074    import org.apache.commons.jexl2.parser.ASTSizeFunction;
075    import org.apache.commons.jexl2.parser.ASTSizeMethod;
076    import org.apache.commons.jexl2.parser.ASTStringLiteral;
077    import org.apache.commons.jexl2.parser.ASTTernaryNode;
078    import org.apache.commons.jexl2.parser.ASTTrueNode;
079    import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
080    import org.apache.commons.jexl2.parser.ASTWhileStatement;
081    import org.apache.commons.jexl2.parser.Node;
082    import org.apache.commons.jexl2.parser.ParserVisitor;
083    
084    import org.apache.commons.jexl2.introspection.Uberspect;
085    import org.apache.commons.jexl2.introspection.JexlMethod;
086    import org.apache.commons.jexl2.introspection.JexlPropertyGet;
087    import org.apache.commons.jexl2.introspection.JexlPropertySet;
088    import org.apache.commons.jexl2.parser.ASTVar;
089    
090    /**
091     * An interpreter of JEXL syntax.
092     *
093     * @since 2.0
094     */
095    public class Interpreter implements ParserVisitor {
096        /** The logger. */
097        protected final Log logger;
098        /** The uberspect. */
099        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    }