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 */
017package org.apache.commons.jexl2;
018
019import java.lang.reflect.Array;
020import java.lang.reflect.InvocationTargetException;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.commons.jexl2.parser.SimpleNode;
028import org.apache.commons.logging.Log;
029
030import org.apache.commons.jexl2.parser.ASTFloatLiteral;
031import org.apache.commons.jexl2.parser.ASTIntegerLiteral;
032import org.apache.commons.jexl2.parser.JexlNode;
033import org.apache.commons.jexl2.parser.ASTAdditiveNode;
034import org.apache.commons.jexl2.parser.ASTAdditiveOperator;
035import org.apache.commons.jexl2.parser.ASTAndNode;
036import org.apache.commons.jexl2.parser.ASTAmbiguous;
037import org.apache.commons.jexl2.parser.ASTArrayAccess;
038import org.apache.commons.jexl2.parser.ASTArrayLiteral;
039import org.apache.commons.jexl2.parser.ASTAssignment;
040import org.apache.commons.jexl2.parser.ASTBitwiseAndNode;
041import org.apache.commons.jexl2.parser.ASTBitwiseComplNode;
042import org.apache.commons.jexl2.parser.ASTBitwiseOrNode;
043import org.apache.commons.jexl2.parser.ASTBitwiseXorNode;
044import org.apache.commons.jexl2.parser.ASTBlock;
045import org.apache.commons.jexl2.parser.ASTConstructorNode;
046import org.apache.commons.jexl2.parser.ASTDivNode;
047import org.apache.commons.jexl2.parser.ASTEQNode;
048import org.apache.commons.jexl2.parser.ASTERNode;
049import org.apache.commons.jexl2.parser.ASTEmptyFunction;
050import org.apache.commons.jexl2.parser.ASTFalseNode;
051import org.apache.commons.jexl2.parser.ASTFunctionNode;
052import org.apache.commons.jexl2.parser.ASTForeachStatement;
053import org.apache.commons.jexl2.parser.ASTGENode;
054import org.apache.commons.jexl2.parser.ASTGTNode;
055import org.apache.commons.jexl2.parser.ASTIdentifier;
056import org.apache.commons.jexl2.parser.ASTIfStatement;
057import org.apache.commons.jexl2.parser.ASTJexlScript;
058import org.apache.commons.jexl2.parser.ASTLENode;
059import org.apache.commons.jexl2.parser.ASTLTNode;
060import org.apache.commons.jexl2.parser.ASTMapEntry;
061import org.apache.commons.jexl2.parser.ASTMapLiteral;
062import org.apache.commons.jexl2.parser.ASTMethodNode;
063import org.apache.commons.jexl2.parser.ASTModNode;
064import org.apache.commons.jexl2.parser.ASTMulNode;
065import org.apache.commons.jexl2.parser.ASTNENode;
066import org.apache.commons.jexl2.parser.ASTNRNode;
067import org.apache.commons.jexl2.parser.ASTNotNode;
068import org.apache.commons.jexl2.parser.ASTNullLiteral;
069import org.apache.commons.jexl2.parser.ASTNumberLiteral;
070import org.apache.commons.jexl2.parser.ASTOrNode;
071import org.apache.commons.jexl2.parser.ASTReference;
072import org.apache.commons.jexl2.parser.ASTReferenceExpression;
073import org.apache.commons.jexl2.parser.ASTReturnStatement;
074import org.apache.commons.jexl2.parser.ASTSizeFunction;
075import org.apache.commons.jexl2.parser.ASTSizeMethod;
076import org.apache.commons.jexl2.parser.ASTStringLiteral;
077import org.apache.commons.jexl2.parser.ASTTernaryNode;
078import org.apache.commons.jexl2.parser.ASTTrueNode;
079import org.apache.commons.jexl2.parser.ASTUnaryMinusNode;
080import org.apache.commons.jexl2.parser.ASTWhileStatement;
081import org.apache.commons.jexl2.parser.Node;
082import org.apache.commons.jexl2.parser.ParserVisitor;
083
084import org.apache.commons.jexl2.introspection.Uberspect;
085import org.apache.commons.jexl2.introspection.JexlMethod;
086import org.apache.commons.jexl2.introspection.JexlPropertyGet;
087import org.apache.commons.jexl2.introspection.JexlPropertySet;
088import org.apache.commons.jexl2.parser.ASTVar;
089
090/**
091 * An interpreter of JEXL syntax.
092 *
093 * @since 2.0
094 */
095public 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}